I am trying to create a secure login that will require a username and password. I am struggling with this and can not figure out the problem. When I try to access my login page I receive an error for <%= form_tag login_url do %> in the login page. If anyone can help me make this work, you will be my hero.
Schema for table that holds username and password
create_table "users", force: true do |t|
t.string "name"
t.string "password_digest"
t.string "password"
t.string "password_confirmation"
t.datetime "created_at"
t.datetime "updated_at"
end
Controller from the table that holds username and password
class User < ActiveRecord::Base
has_secure_password
end
The login page (which I can not get to work)
<h2>The Maintenance Functions are restricted to authorized users. Please login below</h2>
<%= form_tag login_url do %>
<p>
<label for="name">Name:</label>
<%= text_field_tag :name, params[:name] %>
</p>
<p>
<label for="password">Password:</label>
<%= password_field_tag :password, params[:password] %>
</p>
<%= submit_tag "login" %>
<% end %>
The controller for the login page
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_name(params[:name])
if user and user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to secertin_secertout_path :notice => "Logged in successfully"
else
flash.alert = "Invalid userid/password combination"
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url_path :notice => "Logged Out"
end
end
You'll be best using Devise (or maybe Authlogic)
Reason being it's sometimes better to outsource something as important as user authentication. Why? Several reasons:
it's a vital part of your app
it needs to adapt with changes in HTTP security
it needs to work with other dependencies
If you roll your own authentication (which isn't that difficult), your major issue is you won't have the extensibility or reliability that one of these solutions provides. I would highly recommend using devise - it's doing exactly what you need anyway
Remove the password and password_confirmation columns from your table and your code should work. has_secure_password gives you the virtual attributes password and password_confirmation but it stores a hashed version of the password in your table in the password_digest column. Not storing the plain text version of the password in the database is part of what makes has_secure_password secure.
Related
I am building a digital library, and I have completed a lot of the functionalities needed. I am currently having an issue with integrating the digital library with a Learning Management System (LMS).
I already have an admin authentication system for the digital library using the Devise gem. My goal is to allow users who want to access the digital library to login to the digital library using their Learning Management System (LMS) credentials (username and password).
I have been provided with the Login API endpoint and other needed parameters of the Learning Management System (LMS), and I have created the User Model, the Sessions Controller and the Sessions View Templates.
I am currently using the RestClient Gem for the API call, but I having an error undefined local variable or method `username' for # Did you mean? user_path. I can't figure out where things went wrong.
Sessions Controller
require 'rest-client'
class SessionsController < ApplicationController
def new
end
def create
response = RestClient::Request.execute(
method: :post,
url: 'https://newapi.example.com/token',
payload: { 'username': "#{username}",
'password': "#{password}",
'grant_type':'password' },
headers: { apiCode: '93de0db8-333b-4f478-aa92-2b43cdb7aa9f' }
)
case response.code
when 400
flash.now[:alert] = 'Email or password is invalid'
render 'new'
when 200
session[:user_id] = user.id
redirect_to root_url, notice: 'Logged in!'
else
raise "Invalid response #{response.to_str} received."
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: 'Logged out!'
end
end
Sessions New View
<p id=”alert”><%= alert %></p>
<h1>Login</h1>
<%= form_tag sessions_path do %>
<div class="field">
<%= label_tag :username %>
<%= text_field_tag :username %>
</div>
<div class="field">
<%= label_tag :password %>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag 'Login' %>
</div>
<% end %>
User Model
class User < ApplicationRecord
has_secure_password
validates :username, presence: true, uniqueness: true
end
Any form of help with code samples will be greatly appreciated. I am also open to providing more information about this integration if required. Thank you in advance.
I think that the problem is in fact that inside your SessionsController in create action, you are interpolating username and password. There's no definition for these methods in your code so you get undefined local variable or method.
You could probably pick those from params like this:
def username
params[:username]
end
def password
params[:password]
end
Or interpolate them directly in payload replacing current method calls with params[:username] and params[:password].
In such situations, it is good to use byebug or pry to debug your code and see what's happening inside your controller.
You could also think of closing some parts of your logic in Service objects - you shouldn't have more 10-15 lines in your controller action (unless the situation requires it)
Maybe you should use params[:username] rather than only username ?
username and password in payload are undefined variables. Please set their values. Possible values could be params[:username] and params[:password]
I'm building a website with user authentication. And I just noticed that if I create a user with an existing email, it just doesn't work which is normal, but I'd like to give feedback to the user. So for that I need to determine if any user has already that email.
I've tried some things like:
if User.email.include? params[:user][:email]
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
Those are the columns for User:
2.1.0 :014 > User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_token", "admin", "team_id", "teamLeader"]
And the result I get is a big fat error:
undefined method `email' for #<Class:0x00000102b9a908>
So if anybody sees what I'm doing wrong, or knows another way to do it, that would be great.
Cheers
Try this:
if User.exists?(:email => params[:user][:email])
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
...
else
# more code here..
end
Also, you can add validations when you're creating the object:
class User
validates_uniqueness_of :email
More on different validations here: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I believe this way of doing the validation is wrong, you should validate the uniqueness of the email in the User model itself like below
validates :email, uniqueness: true #User model
This way the validation would be on the the User model. The problem with the condition you are using is that it is accessing an instance method specific to objects as a class method. So User.email means that there is a method called email that has the same logic for all the instances of the user class or more formally a class method which you don't have here. The email is an attribute specific to each user an instance attribute/variable (Each user has a different email).
You can see/show the validation errors present on the model using #user.errors.full_messages where #user is the instance you are trying to register/save.
This is how I would normally do it if this action is for registering users i.e. creating new users.
class User < ActiveRecord::Base
#attribute accessors and accessible
validates :email, uniqueness: true
end
class UsersController < ApplicationController
def create
#user = User.new params[:user]
if #user.save
#code for redirect or rendering the page you want
else
render 'new'
end
end
end
#new.html.erb
<%= form_for #user do |f| %>
<% if #user.errors.any? %>
<div>
<ul>
<% #job.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#form fields
<% end %>
This way you display all the error messages to the user at the top of the registration form.
I'm trying to get the controller's "destroy" to work correctly and I'm wondering what the correct set up should be.
The error that I'm getting is
ActiveRecord::RecordNotFound in AuthenticationsController#destroy
Couldn't find Authentication without an ID
My controller looks like
class AuthenticationsController < InheritedResources::Base
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
redirect_to(:back)
end
database table
create_table "authentications", :force => true do |t|
t.integer "user_id"
t.string "provider"
t.string "uid"
t.string "secret"
t.string "token"
end
I have tried other parameters such as :user_id
How can I get users to destroy their tokens? (with the option to re-authenticate later)
You're not passing id to controller
try
<%= link_to "Disconnect Your Authentication", {:controller=>'authentications', :action=>'destroy', :id=>current_user.authentication_id} %>
or use path helper with #autentication argument as option.
(You will need to edit your routes file)
If you're wanting to destroy all authentications for a user, you could certainly change your controller's destroy method to be:
def destroy
current_user.authentications.destroy_all
end
A more conventional approach would be to destroy a particular authentication. In that case the link_to method needs a path that includes an id parameter (which will end up as your params[:id] value in the controller). You can imagine a view snippet like the following that displays all a user's authentications, each with a destroy link:
<ul>
<% current_user.authentications.each do |a| %>
<li>
<%= a.provider %>
-
<%= link_to 'Disconnect Your Authentication', authentication_path(a), :method => :delete %>
</li>
<% end %>
</ul>
This assumes current_user is a helper and that your routes are set up on your authentication model. The authentication_path helper uses the a authentication instance to generate a path, complete with an id parameter.
I followed railscast #250 Authentication from Scratch & got everthing wworking fine. Now I'm trying to only display edit & destroy links on my index page to admin user's.
I've set up mu User database with a admin boolean field & tried putting a simple if statement in the view of another model (hikingtrails) to only display certain links to admin users but I get this error when I try it out, undefined method 'admin?' for nil:NilClass
Database Schema
create_table "users", :force => true do |t|
t.string "email"
t.string "password_digest"
t.boolean "admin"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
User Model
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation#, :admin
validates :email, :uniqueness => true
has_secure_password
end
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
# fetch the currently logged-in user record to see if the user is currently logged in
# putting this method in ApplicationController so that it’s available in all controllers
private
def current_user
# checks for a User based on the session’s user id that was stored when they logged in, and stores result in an instance variable
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
# to give access to this method from all the views, the helper_method makes it a helper method
helper_method :current_user
# basic authorization, user must be logged in!
def authorize
redirect_to login_url, alert: "You must be logged in to perform this action" if current_user.nil?
end
end
views/hikingtrails/index.html.erb
<% if current_user.admin? %>
<%= link_to t('.edit', :default => t("helpers.links.edit")),
edit_hikingtrail_path(hikingtrail), :class => 'btn btn-mini' %>
<%= link_to t('.destroy', :default => t("helpers.links.destroy")),
hikingtrail_path(hikingtrail),
:method => :delete,
:data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) },
:class => 'btn btn-mini btn-danger' %>
<% end %>
current_user will be nil if a user is not logged in according to your code. So you need to do this:
<% if current_user && current_user.admin? %>
or using the try method Rails adds to all objects.
<% if current_user.try(:admin?) %>
as Dogbert said, current_user will be nil if the user is not logged in.
I would suggest two other alternatives:
1) in the current_user method return a special type "guest" user instead of nil. Il will be useful in case you want to do something else with it later, for example in response to some user action.
As inspiration, look at how Ryan Bates explains the Ability class of his gem cancan: link.
The first thing he does is creating an unitilized (and not persisted in DB) user. An that Ability class will be instantiated each time Rails will parse an ERB template with that kind of user verification.
So, you could do:
def current_user
#current_user ||= ((User.find(session[:user_id]) if session[:user_id]) || User.new)
end
So, if (User.find(session[:user_id]) if session[:user_id]) returns nil, the #current_user will be set to an uninitialized User with no identity in DB.
2) define a new metod just to check if the user is an admin, for example:
# your unmodified current_user implementation
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def is_an_admin?
if current_user && current_user.admin?
end
So that you can use it in this way:
<% if is_an_admin? %>
<div>
<%= do stuff....%>
...It might be an extra method call, but it might also make your code more readable.
I know this is old, but if someone is googling the error as I did, there is actually no error in Rails Tutorial, but they forgot to highlight one thing they added.
Listing 9.54
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
Note that they added :destroy action here, not added before, which makes sure that the user is logged to perform destroy action and just then checks if he's an admin
before_action :admin_user, only: :destroy
Correction:
As of the time of this edit 12/14/2015, the rails tutorial now adds the :destroy action in Listing 9.53. If you miss that one as I did, you will get this error.
it looks like in your User model:
attr_accessible :email, :password, :password_confirmation#, :admin
admin is commented out, you need to delete the #.
Im a RoR rookie and am using rails 3.2.3.
I've been using devise and so far it has been great, however, I've run into a problem.
I have a User table with devise and a HABTM association with a Role table. I have the join table created and everything is fine. When I create a user and choose it's role, it creates the data in the join table correctly.
However, I activated devises' confirmable option and things started to go wrong.
When I create a new user, it no longer inserts the record in the join table as it should.
I mean, all I have literary done was add , :confirmable in front of the other devise options such as :database_authenticatable, :recoverable, :rememberable, :trackable and :validatable.
When I activated :confirmable I wrote this migration (which I saw on stack overflow also):
class AddConfirmableToDeviseV < ActiveRecord::Migration
def change
change_table(:users) do |t|
t.confirmable
end
add_index :users, :confirmation_token, :unique => true
end
end
It sends the email with the link to confirm, nothing wrong with that, but when I click it, the app breaks as that user does not have a role assigned to it, and that is a must.
And as I said, all I did was add :confirmable. If I comment it out like this #,:confirmable in my User model, the role and user data gets inserted in the join table correctly.
What's going on? Any tips?
Thanks in advance,
Regards
Update
#Kyle C
I'm creating the user with the regular actions:
View:
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username %>
</div>
(...)
<% for role in Role.find(:all) %>
<div class="field">
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%= role.name %>
</div>
<%end%>
Then in my controller:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
(...)
Without :confirmable, this is enough to enter the data in the join table.
On top of this, I have this in my app controller:
def after_sign_in_path_for(resource)
if current_user.roles.first.id == 1
admin_dashboard_path
elsif current_user.roles.first.id == 2
manage_path
end
end
If i take this out, the user gets logged in when he clicks the confirmation email, however, the middle join table is still doesn't get the association.
I've browsed the documentation (https://github.com/plataformatec/devise/blob/master/lib/devise/models/confirmable.rb)
but I'm still a rookie and I also didn't find anything that would override my app's initial behaviour.
Is there a way to force the input of the records in my join table after I create the user?
I've tried this:
def create
#user = User.new(params[:user])
#role = Role.find(params[:user][:role_ids])
if #user.save
#user.role << #role
#user.save
AND (wrong thing to do but still without success)
(...)
if #user.save
query = ActiveRecord::Base.connection.raw_connection.prepare("INSERT INTO roles_users (role_id, user_id) VALUES (?,?);")
query.execute(#role.id, #user.id)
query.close
This is really frustrating, anyone else came up with this issue when activating :confirmable with a HABTM?
Thanks for all your help
t.confirmable is no longer supported please use this migration
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email