Rectangle 27 3

If you have more than one Devise model in your application (such as User and Admin), you will notice that Devise uses the same views for all models. Fortunately, Devise offers an easy way to customize views. All you need to do is set config.scoped_views = true inside the config/initializers/devise.rb file.

After doing so, you will be able to have views based on the role like users/sessions/new and admins/sessions/new. If no view is found within the scope, Devise will use the default view at devise/sessions/new. You can also use the generator to generate scoped views:

rails generate devise:views users

thanks! I actually referred to that first and it was still not working, but when I ran rails server again it did.

How to give two different templates for two models using Ruby on Rails...

ruby-on-rails ruby ruby-on-rails-3 devise
Rectangle 27 2

Devise allows you to set up as many Devise models as you want.

You can configure views for each model

ruby - How to use two different custom signup forms with two different...

ruby-on-rails ruby ruby-on-rails-3 devise rails-routing
Rectangle 27 5

This has taken me quite a while to figure out, but I've eventually got a working solution.

I also want to give most of the credit for this to Jordan MacDonald who posted the question I mentioned above in the Devise Google Group. While that thread didn't have an answer on it, I found the project he had been working on, read the code and adapted it to my needs. The project is Triage and I highly recommend reading the implementations of SessionController and the routes.

As above, my model is as follows, and I'm using the gem devise_ldap_authenticatable. In this example, I have two users, LdapUser and LocalUser, but I see no reason why this wouldn't work for any two Devise user models, as long as you have some way of differentiating between them.

class User < ActiveRecord::Base
end

class LdapUser < User
  devise :ldap_authenticatable, :rememberable, :trackable
end

class LocalUser < User
  devise :database_authenticatable, :registerable, :confirmable, :recoverable, :trackable
end

The first part we need is the controller. It should inherit from Devise::SessionsController, and it chooses which type of user we are authenticating, then explicitly passing this on to the authentication stage, which is handled by Warden.

As I was using LDAP against an Active Directory domain for one part of the authentication, I could easily tell which details should be authenticated against LDAP, and which shouldn't, but this is implementation specific.

class SessionsController < Devise::SessionsController
  def create

    # Figure out which type of user we are authenticating.
    # The 'type_if_user' method is implementation specific, and not provided.

    user_class = nil
    error_string = 'Login failed'
    if type_of_user(request.params['user']) == :something
      user_class = :local_user
      error_string = 'Username or password incorrect'
    else
      user_class = :ldap_user
      error_string = 'LDAP details incorrect'
    end

    # Copy user data to ldap_user and local_user
    request.params['ldap_user'] = request.params['local_user'] = request.params['user']

    # Use Warden to authenticate the user, if we get nil back, it failed.
    self.resource = warden.authenticate scope: user_class
    if self.resource.nil?
      flash[:error] = error_string
      return redirect_to new_session_path
    end

    # Now we know the user is authenticated, sign them in to the site with Devise
    # At this point, self.resource is a valid user account.
    sign_in(user_class, self.resource)
    respond_with self.resource, :location => after_sign_in_path_for(self.resource)
  end

  def destroy
      # Destroy session
  end

  def new
      # Set up a blank resource for the view rendering
      self.resource = User.new
  end
end

Devise sets up lots of routes for each type of user, and for the most part we want to let it do this, but as we are overriding the SessionsController, so need it to skip this part.

After it has set up its routes, we then want to add our own handlers for sign_in and sign_out. Note that the devise scope being local_user doesn't matter, it just needs a default scope, we are overriding this in the controller anyway. Also note that this is local_user singular, this caught me out and caused lots of trouble.

devise_for :ldap_users, :local_users, skip: [ :sessions ]

devise_scope :local_user do
  get 'sign_in' => 'sessions#new', :as => :new_session
  post 'sign_in' => 'sessions#create', :as => :create_session
  delete 'sign_out' => 'sessions#destroy', :as => :destroy_session
end

The view is very simple, and can modified without causing too many issues.

<div>
  <%= form_for(resource, :as => 'user', url: create_session_path) do %>
    <fieldset>
      <legend>Log In</legend>
      <label>LDAP Username or Database Email</label>
      <input type="text" placeholder="Username or Email" name="user[email]" />
      <label>Password</label>
      <input type="password" placeholder="Password" name="user[password]" />
      <input type="submit" class="button" value="Log In" />
    </fieldset>
  <% end %>
</div>

I hope this helps someone else. This is the second web app I've worked on that had to have both LDAP and local authentication (the first being a C# MVC4 application), and both times I've had significant trouble getting authentication frameworks to handle this nicely.

ruby on rails - Using one sign-in form with two Devise user models and...

ruby-on-rails ruby devise ldap
Rectangle 27 3

config.include Devise::TestHelpers, :type => :controller

ruby on rails - Testing models with RSpec using devise - Stack Overflo...

ruby-on-rails ruby rspec devise
Rectangle 27 4

Your problem does not seem clear to me. Although I am guessing you want to have custom controller for devise. You need to create a new controller like say registration controller and override the devise registration controller

class RegistrationsController < Devise::RegistrationsController
  def create
    #Insert Your Logic here
  end
end

Also you would have to add the below in the routes and add the paths as per your need.

devise_for :users, :controllers => { :registrations => "registrations" }, :path => "", :path_names => {:sign_out => "signout", :sign_up => "signup"}

You can also have different register actions for the different roles. You could define professional_user_register method for professional users and employee_register for employees. You could have to define these actions in the routes. See below samples

devise_scope :user do
    post "professional/register" =>"registrations#employee_register"
end

devise_scope :user do
    post "employee/register" =>"registrations#professional_user_register"
end
def employee_register
  #Employee Register Code
end

def professional_user_register
  #Professonal User Register Code
end

I think this is what I want to do. One question....If I wanted to display different views depending on what link the user, professional or employer, came from what would i do. I think I would put some logic in the create saying if professional and else and display the views for sign up. I am just not sure how to do it because they, the user, has not been defined as of yet. They have not signed up. Please correct me if I am wrong.

See above code. I have edited the answer. You could have different actions for different roles. Hope it solves your problem.

ruby - How to use two different custom signup forms with two different...

ruby-on-rails ruby ruby-on-rails-3 devise rails-routing
Rectangle 27 1

devise_for :flyers

ruby on rails - How to configure routes for different user models usin...

ruby-on-rails ruby-on-rails-3 ruby-on-rails-4 devise rails-routing
Rectangle 27 0

Devise allows you to set up as many Devise models as you want.

You can configure views for each model

ruby - How to use two different custom signup forms with two different...

ruby-on-rails ruby ruby-on-rails-3 devise rails-routing
Rectangle 27 0

You probably want an elsif here, rather than 3 separate blocks. Try something to the effect of:

if user_signed_in?
  ...
elsif admin_signed_in?
  ...
elsif company_user_signed_in?
  ...
elsif params[:action] == 'index'
  ...
elsif params[:action] == 'admin'
  ...
elsif params[:action] == 'company_user'
  ...
else
  # handle any other case where none of above conditions are met, e.g. params[:action] == 'foobar'
end

Thanks, that's along the same thought I had with the eventual code that used. See my code for explanation.

No problemo. For future reference, it's a bit of poor form on StackOverflow for you to take someone's answer, re-type it, and then award yourself the correct solution. If a concept helps you solve your question in the future, you should mark it as the answer rather than re-hashing it.

That's what I would do if your answer actually helped solve my problem but I solved it with help from other sources. If you look at my solution, you can see that it's very different than what you wrote even though it has similar ideas. In fact, my original broken code also had the same idea but it doesn't really work.

Solution looks the same to me, but what do I know about what went through your head. For what it's worth, your last else statement has a condition params[:action] == 'company_user' that doesn't do anything.

I just tried your first method and it seems to works as well. I can see why you chose to refactor it. It's cleaner. Thanks.

ruby on rails - Need a simple syntax when using multiple devise user m...

ruby-on-rails ruby devise ruby-on-rails-4
Rectangle 27 0

If you want to create a Profile when creating a Listing, you can do it with the ActiveAdmin's create controller of Listing:

The code should be something like this: (put it in app/admin/listing.rb)

controller do
  def create
    # Create the profile.
    @profile = Profile.new
    # Create the listing and connect the profile to it.
    @listing = Listing.create(permitted_params[:name])
    @listing.profile = @profile

    create! do |success, failure|
      succes.html { redirect_to admin_dashboard_path}  # Or whatever path.
    end
  end
end

ruby on rails - Linking models created using Devise + Activeadmin - St...

ruby-on-rails ruby-on-rails-4 devise activeadmin has-one
Rectangle 27 0

This has taken me quite a while to figure out, but I've eventually got a working solution.

I also want to give most of the credit for this to Jordan MacDonald who posted the question I mentioned above in the Devise Google Group. While that thread didn't have an answer on it, I found the project he had been working on, read the code and adapted it to my needs. The project is Triage and I highly recommend reading the implementations of SessionController and the routes.

As above, my model is as follows, and I'm using the gem devise_ldap_authenticatable. In this example, I have two users, LdapUser and LocalUser, but I see no reason why this wouldn't work for any two Devise user models, as long as you have some way of differentiating between them.

class User < ActiveRecord::Base
end

class LdapUser < User
  devise :ldap_authenticatable, :rememberable, :trackable
end

class LocalUser < User
  devise :database_authenticatable, :registerable, :confirmable, :recoverable, :trackable
end

The first part we need is the controller. It should inherit from Devise::SessionsController, and it chooses which type of user we are authenticating, then explicitly passing this on to the authentication stage, which is handled by Warden.

As I was using LDAP against an Active Directory domain for one part of the authentication, I could easily tell which details should be authenticated against LDAP, and which shouldn't, but this is implementation specific.

class SessionsController < Devise::SessionsController
  def create

    # Figure out which type of user we are authenticating.
    # The 'type_if_user' method is implementation specific, and not provided.

    user_class = nil
    error_string = 'Login failed'
    if type_of_user(request.params['user']) == :something
      user_class = :local_user
      error_string = 'Username or password incorrect'
    else
      user_class = :ldap_user
      error_string = 'LDAP details incorrect'
    end

    # Copy user data to ldap_user and local_user
    request.params['ldap_user'] = request.params['local_user'] = request.params['user']

    # Use Warden to authenticate the user, if we get nil back, it failed.
    self.resource = warden.authenticate scope: user_class
    if self.resource.nil?
      flash[:error] = error_string
      return redirect_to new_session_path
    end

    # Now we know the user is authenticated, sign them in to the site with Devise
    # At this point, self.resource is a valid user account.
    sign_in(user_class, self.resource)
    respond_with self.resource, :location => after_sign_in_path_for(self.resource)
  end

  def destroy
      # Destroy session
  end

  def new
      # Set up a blank resource for the view rendering
      self.resource = User.new
  end
end

Devise sets up lots of routes for each type of user, and for the most part we want to let it do this, but as we are overriding the SessionsController, so need it to skip this part.

After it has set up its routes, we then want to add our own handlers for sign_in and sign_out. Note that the devise scope being local_user doesn't matter, it just needs a default scope, we are overriding this in the controller anyway. Also note that this is local_user singular, this caught me out and caused lots of trouble.

devise_for :ldap_users, :local_users, skip: [ :sessions ]

devise_scope :local_user do
  get 'sign_in' => 'sessions#new', :as => :new_session
  post 'sign_in' => 'sessions#create', :as => :create_session
  delete 'sign_out' => 'sessions#destroy', :as => :destroy_session
end

The view is very simple, and can modified without causing too many issues.

<div>
  <%= form_for(resource, :as => 'user', url: create_session_path) do %>
    <fieldset>
      <legend>Log In</legend>
      <label>LDAP Username or Database Email</label>
      <input type="text" placeholder="Username or Email" name="user[email]" />
      <label>Password</label>
      <input type="password" placeholder="Password" name="user[password]" />
      <input type="submit" class="button" value="Log In" />
    </fieldset>
  <% end %>
</div>

I hope this helps someone else. This is the second web app I've worked on that had to have both LDAP and local authentication (the first being a C# MVC4 application), and both times I've had significant trouble getting authentication frameworks to handle this nicely.

ruby on rails - Using one sign-in form with two Devise user models and...

ruby-on-rails ruby devise ldap
Rectangle 27 0

You could make two separate models, Student and Teacher. Then add an admin:boolean field for teacher. I am assuming most admins will probably be teachers? Even if that is not the case you could just default that all admins are teachers. Three separate models is terribly bulky.

ruby on rails - Should I use Multiple user models or one big user mode...

ruby-on-rails devise
Rectangle 27 0

I think the only way to handle this would be to have your own custom sign in form and controller that determined the type of user and then sign them in correctly. I would recommend an approach like what mark mentioned for simplicity (take a look at something like CanCan to manage roles).

Another potential problem with having multiple user models is that you will have multiple versions of all the devise helper methods. So for current_<resource> and <resource>_signed_in? you would have current_user, current_business_user, user_signed_in? and business_user_signed_in?. Then you would either have to implement your own versions of these methods or you would need to check both versions everywhere you used them.

ruby on rails - Using Devise for Two Different Models but the same Log...

ruby-on-rails ruby-on-rails-3 authentication devise
Rectangle 27 0

Store your params in variable first then use them in query to avoid quote errors

ruby - Instantiating Devise user models manually using contents of par...

ruby ruby-on-rails-3 devise
Rectangle 27 0

From re-reading the Rolify documentation I have come up with the beginnings of the solution to my problem.

Whenever a user creates a category, subcategory or article I'll run

user.add_role :current_role Model.Instance_id

which I can then query from the admin portal by getting the instances id. Then by querying all of the user roles within the system and comparing them against the instances associated roles, I can create the view partial for the admin console.

model_instance = Model.find(instance_id)
model_instance.roles #returns all of the roles associated with that instance

I would also need to create a few methods to handle the (mass)assignment/reassignment of roles so that when a checkbox is (de)selected that the expected result is achieved, such as adding a role/group to a instance and vice versa. Probably something along the lines of (ruby flavoured pseudocode to follow!!)

users = User.with_any_role(:some_role)

def assignRoleToModel(model_instance, users, role)
  if model_instance.roles.empty?
    users.each { |u| u.add_role creatorRole model_instance }
  end
  flash[:warning] = "#{Model_instance.name} already has that role assigned to it!"
end

where model_instance is the instance of the model I want to control group/role access to, users is a list of users who have the role I want to add to the model_instance and role is the role I wish to allow access to the model_instance.

devise - How to restrict access to models in Ruby on Rails 4 using Pun...

ruby-on-rails-4 devise rolify pundit awesome-nested-set
Rectangle 27 0

You can still use that if you want to create the rspec structures but you'll likely want to decline overwriting your model. For example, here's my rspec_model output:

$ script/generate rspec_model Person
      exists  app/models/
      create  spec/models/
      create  spec/fixtures/
overwrite app/models/person.rb? (enter "h" for help) [Ynaqdh] n
        skip  app/models/person.rb
      create  spec/models/person_spec.rb
      create  spec/fixtures/people.yml
      exists  db/migrate
      create  db/migrate/20100826043436_create_people.rb
app/models/person.rb

ruby on rails - How to test devise models, controllers and views using...

ruby-on-rails testing devise
Rectangle 27 0

Store your params in variable first then use them in query to avoid quote errors

ruby - Instantiating Devise user models manually using contents of par...

ruby ruby-on-rails-3 devise
Rectangle 27 0

config.scoped_views = true

If you have more than one role in your application (such as User and Admin), you will notice that Devise uses the same views for all roles. Fortunately, Devise offers an easy way to customize views. All you need to do is set config.scoped_views = true inside config/initializers/devise.rb.

After doing so, you will be able to have views based on the role like users/sessions/new and admins/sessions/new. If no view is found within the scope, Devise will use the default view at devise/sessions/new. You can also use the generator to generate scoped views:

ruby on rails - How to use two different sets of mailer templates for ...

ruby-on-rails devise actionmailer resque
Rectangle 27 0

A single User model will be a good option for all user roles.

You can take another model as Role for different types of roles.

class Role < ActiveRecord::Base 
  has_many :users    
end
class User < ActiveRecord::Base  
  belongs_to :role
end

You can use gem 'cancan' for role based authorization

On home page You can render different partials for sign in and registration in different sections.

<%=render :partial => 'devise/sessions/form' %>
<%= render :partial => 'devise/registrations/form' %>

Having a single user model would be a bad idea in my case. The admin model has lots of fields to be filled up than in the user model.

Single sign in for admin and user models using devise in ruby on rails...

ruby-on-rails ruby login
Rectangle 27 0

I would suggest that you rethink your problem pretending that devise and angular have nothing to do with each other.

Let's assume that you're not going to render any html templates in rails.

  • You create a sign in route in your angular app, it has a form pointing to your server's devise sign-in route /api/v1/user/sign-in.
  • When a user hits submit on the form angular can do some front end validation and send the data to your server.
  • In your rails controller devise will authenticate who this person is, after devise authenticates them your server route should send back the user's information as a json response (or the error messages).

The angular service which made the request to your devise endpoint will now have access to the user's information (from the server's json response). It can check properties on this user object like their role, and then it can use angular's location service to change the route to wherever this role should go.

When the route changes angular will render the route's view and everything will continue on its merry way.

If you need to change the UI within a route based on the role see this question RESTful Authorization in the UI

When the user comes back to your site later you can make the angular app detect that they're still logged in. The angular service that you wrote to to sign a user in can also be responsible for attempting to get the current user, and if there is none, redirect to the sign in page.

Devise gives you a helper called current_user which will give you access to an instance of the user that is logged in. You should create a server route like /api/v1/current_user and have it return the users information. If the user is logged in, redirect to their home page just like after a sign in. Otherwise redirect to the sign in page.

If you don't know angular very well I'd suggest that you spend some time on this site https://egghead.io/

So as I mentioned I have two different models instead of two different roles for one model. So I have to do stuff like => when a user tries to access a given url, figure out which role it requires, redirect to corresponding login page. Also, I have two different login. To give some context, I have a decent sized non-Angular Rails app that we are moving to Angular.

In your /api/v1/current_user endpoint you could return current_user || current_company similar to this question stackoverflow.com/questions/10549038/ You don't need to have a role property on the json it returns. As long as your angular code can determine the type of the model from the json than it can use the location service to redirect to the correct rotue. Stack Overflow probably isn't your best bet for a broad architectural discussion, maybe try a mailing list groups.google.com/forum/#!forum/rubyonrails-talk

Thanks for the tip. Posted there as well. I think I broadly agree with the approach you suggested, its just that setting up devise initially required me to do almost nothing to fire it up, so I was looking for essentially an option wherein I could leverage as much functionality of Devise as possible. What you suggest is sort of "from the basics" which is obviously correct, but slightly more involved.

ruby on rails - AngularJS: Use devise with multiple models - Stack Ove...

ruby-on-rails angularjs ruby-on-rails-4 devise
Rectangle 27 0

Building a two headed Auth Monster

So what you are describing is possible, but it will definitely be a pain. You will have to build up a bunch of code to determine if a user is a NormalUser vs AdminUser in the Rails side. In the Angular side you will have to determine which login a user needs, NormalUser vs AdminUser. That being said, it is possible and here are some pointers that should help.

Example routes for having two models in Devise:

devise_for :normal_users

devise_for :admin_users

# route for the sample controller below
resource :users do
  collection do
    get :current
  end
end

Devise will create helper methods in your controllers along the lines of:

current_normal_user

authenticate_normal_user!

current_admin_user

authenticate_admin_user!

So you would have to check both to see if a sessions has been authenticated, best plan is to to wrap both checks into a custom before_action, something along the lines of before_action :authenticate_all_users.

Here is an example UsersControllers that returns JSON Angular will use to check for authentication.

class UsersController < ApplicationController
  # Needs a before_action that authenticates the user

  respond_to :json

  def current
    @user = current_normal_user || current_admin_user

    respond_with @user
  end

end

I found this interceptor very useful when handling auth in Angular. Add this to your app.config when setting up Angular, it redirects to a specified page if 401 status code is returned by any http request. This is simpler than having all Resources forced to handle the possibility that a user is not authenticated. (as coffeescipt)

# Monitors the requests and responses of angular
#  * Redirects to /users/sign_in if status is a 401
app.config [
  "$httpProvider"
  ($httpProvider) ->
    $httpProvider.interceptors.push ($q) ->
      request: (config) ->
        config or $q.when(config)

      requestError: (rejection) ->
        $q.reject rejection

      response: (response) ->
        response or $q.when(response)

      responseError: (rejection) ->

        # Not logged in, redirect to login
        if rejection.status is 401
          $q.reject rejection

           # Change this with desired page
          window.location = "/users/sign_in"

        else
          $q.reject rejection

Last is an example Angular service that grabs the user information. If the user is not authenticated, the 401 status will be caught by the previous $httpProvider and the user will be dealt with accordingly. Otherwise, if the user is authenticated, the user's information will populated to the $rootScope.currentUser. You simply have to add this as a dependency in a controller that should be auth protected. (in coffeescript)

angular.module("TheAngularApp.services").service "CurrentUserService", ($rootScope, $http, CurrentUser, User) ->
    userService =
        reloadCurrentUser: ->
            @currentUser = CurrentUser.show((user) ->
                # set in scope
                $rootScope.currentUser = user
            )
            @currentUser

    userService.reloadCurrentUser()
    userService

This service depends on the CurrentUser Resource that points at /users/current.json endpoint. In your scenario with the CurrentUserService, possible options for dealing with multiple models are:

  • Check twice if the user is authenticated, once for normal_user and again to admin_user
  • Create a custom controller method that checks the auth of normal_user and admin_user, this is roughly what the sample rails controller is doing.

Optionally, I would look into ng-idle as a way to allow sessions to expire within Angular.

My logged in page starts displaying while the backend request for user info is happening, and consequently the redirect to login page is quite visible and leads to a bad ux.

If this is not an possible, you will have to make the auth request from Angular. This means you will need to have the user wait until you can check the promise from the auth request. Once it is valid, you move the user to the correct route.

WARNING I believe Devise sends a status code of 401 if you fail to log in, which will trip the interceptor previous discussed. To get around this, the interceptor will have to exclude paths that handle the auth requests.

Session.create {email: email, password: password}, success = (user) ->
   # User was successfully authenticated
   $scope.currentUser = user
   $location.path( "/" );  
, error = (data, status, headers, config) ->
   # Failed to auth, notify user
)

Not the exact answer, but comes closest to what I want, given the question was very open ended.

Interceptor info proving very helpful as I implement this. One question. My logged in page starts displaying while the backend request for user info is happening, and consequently the redirect to login page is quite visible and leads to a bad ux. Wanted to know if there is a good general practice on when to start displaying the page, so as to prevent such issues

The easiest way is to use a non-angular page for the login, such as the one provided by Devise. Once a user successfully logs in, the page they are directed to loads up the Angular App. Hrm, let me expand above. . .

already gave that a lot of thought. The problem there is that its problematic to share internal links, as then i can't redirect back to them after login since rails doesn't get the part after #, and I will be re-bootstrapping angular module after signin.

ruby on rails - AngularJS: Use devise with multiple models - Stack Ove...

ruby-on-rails angularjs ruby-on-rails-4 devise