There is a Rails hotel list app with Devise/CanCan set up
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:show]
load_and_authorize_resource
1) What is the most elegant way to let owners edit their hotel info without registering them as users?
2) What if we create a unique token for every hotel and email something like the following link to the corresponding owner:
http://myapp.io/hotels/10010/edit?token=AAAABBBBCCCCDDD
..how to configure Devise/CanCan so they authenticate the user and allow them edit the corresponding record in the Hotels table?
Thank you in advance!
Regards,
Serge.
A quick and dirty solution would be to override the authenticate_user! method and add a check for the token:
# application_controller.rb
def authenticate_user!
if (token = params[:token] || session[:token])
#current_user = User.find_by_token token
session[:token] = token if #current_user
else
super
end
end
First you'd have to add the token column to the user table and you'd still need to create a user for them. Generate a token and email them the link with their token. When they first arrive at the site with the token in the URL, the above method will check if there's a matching record in the db and set it as the current_user and save the token to the session so that subsequent requests will still work. Don't forget to clear that session later (logout or expires).
The reason you still need to create a user record is that you don't want to start overriding your cancan logic and you need a place to store the token anyway.
There's also some gems that will add token authentication:
https://github.com/gonzalo-bulnes/simple_token_authentication
And a simple Rails built in solution:
http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
Related
I have rails app with:
Admin table with Devise authentication
User table with email and name without authentication (but session to remember them)
User can browse anywhere but now on certain pages I would like to enhance it and add authentication - allow user to create password and only with password it will be accessible but I am quite lost what is the best way to do it with the current setting?
I allow users to add their details like name and email and I am creating a cookie to remember them without any authentication or password:
UsersController
def create
user = User.find_or_create_by(email: params[:user][:email])
cookies.permanent.signed[:user_id] = user.id
session[:user_id] = user.id # for users/edit temporary
render json: user
end
Let's say I have this following method in User:
before_filter :authenticate_user!, only: :your_order
def your_order
end
If User will visit this page and didn't set up password before, how can I prompt him to create one and how can I require for him to login after with Devise? I am thinking of more solutions but none of them are perfect.
As per the specifications given the below mentioned criteria might help you.
def your_order #before_filter
if user.password.present?
# authenticate using valid_password? method of devise
else
#redirect user to say set_password
end
end
def set_password
#set the user password in this method and after successful completion redirect to login page where before filter your_order will be called
end
I'm working on google authentication for a rails app. Currently using the omniauth-google-oauth2 gem to implement Google auth. I've managed to have users sign in using google. However, I'd also like users to be able to sign up using google. My problem is that I've matched the google callback URL to a particular controller action (sessions#create).
Is it possible to choose between 2 redirect URIs based on whether users are signing in or signing up? Currently, my only idea is to create new google client credentials to be used for sign up, I hope there is a better way.
You don't need to have 2 redirect uris, you just need to do some more work when receiving the callback. For instance:
class SessionsController < ApplicationController
...
def create
email = auth_hash['info']['email'] # assuming your omniauth hash is auth_hash and you're requiring the email scope
#user = User.find_by(email: email) if !email.blank? # assuming your user model is User
if #user
login_user(#user) # use your login method
elsif !email.blank?
#user = User.new(name: auth_hash['info']['name'], email: email)
unless #user.save!(validate: false) # validate false because I'm enforcing passwords on devise - hence I need to allow passwordless register here)
# deal with error on saving
end
else
# deal with no found user and no email
end
end
protected
def auth_hash
request.env['omniauth.auth']
end
end
I've written all steps but the creation process can be shortened to:
#user = User.create_with(name: auth_hash['info']['name']).find_or_initialize_by(email: email)
#user.save! if #user.new_record?
if #user
login_user(#user)
else
# deal with no user
end
Nonetheless, you can't be sure the user is going to give you scope access to the email, so personally I think the first version, even if a bit lengthier is more robust. Then on the shorter version there's also the problem of, if #user is false, why is so? And will require you to add more logic to figure out why is that, whereas in the first one it's much easier to apply the correct response to each situation.
I am using Clearance gem for authentication. I have one page in the app to which I want to permit anyone to come provided they have a secure token in the URL / Session. They need not be users in the system.
Is this doable with Clearance. Any hints on how I should proceed. Initially I thought I should override require_login and current_user. Is that a good approach?
Should the secure token page also allow access to people who are signed in, or must everyone have the secure token?
If you must have the secure token, regardless of whether you are signed in with Clearance or not, then I would avoid Clearance for this controller all-together by not calling require_login at all (or calling skip_before_action :require_login if the filter is already in your controller's inheritance tree). Then you could implement your own before_action that checks the token and does whatever you'd like. You could also implement your own current_user for this controller if desired.
If the page should also allow signed in users then I would still skip the Clearance before action and instead use something like this:
def require_login_or_secret_token
unless params["super_secret_security_token"] == TOKEN
require_login
else
end
Then you'd need to override current_user to return a guest object rather than nil if you want to use current_user in this action/view:
def current_user
super || Guest.new
end
I'm new to rails and are have a pretty basic understanding of the Devise Gem. Besides the CRUD and views I'm not clear on what it provides that could help me for a AngularJs app talking to a Rails Json Api.
At the moment I'm hand rolling things ie. for security I have I exchange a HTTP Header token between client (js) and server. I'm also using the Railscast #250 for user authentication - but as I don't see how to apply the SessionController for a remote client.
Are there any strategies I could employ for authentication and managing session via a remote json API?
Thanks!
I personally wouldn't use devise for something like this because there's only a small part of it you'd be using anyways
Dont
You pretty much just don't use a session. All you need to do is pass in basic authentication each time, and in the application controller you determine if its valid, if not just send them back an auth error.
Example request: http://username:password#example.com/api/endpoint
class ApplicationController
before_filter :check_auth!
private
def check_auth!
username, password = ActionController::HttpAuthentication::Basic::user_name_and_password(request)
user = User.find_by(username: username)
if user && user.encrypted_password == SomeEncryptFunction(params[:password])
#current_user = user
else
raise "error"
end
end
end
But if you want to...
Then what you can do is update a DateTime field on the user when they first auth (which starts their session), then on subsequent calls they can just pass a token you give them that you you check for each time they sign in. You also check that only a certain amount of time has passed since they first authed, otherwise their session is invalid.
class SessionsController < ApplicationController
skip_before_filter :check_auth!
before_filter :login!
private
# Note: I don't remember the actual devise method for validating username + password
def login!
user = User.find_by(username: params[:username])
if user && user.valid_password(params[:password])
current_user = user
current_user.update_attributes(
authenticated_at: DateTime.now,
authentication_token: Devise.friendly_token
)
else
raise "error"
end
end
end
class ApplicationController
before_filter :check_auth!
private
def check_auth!
if valid_token(params[:token])
current_user = User.find_by(authentication_token: params[:token])
else
raise "error"
end
end
# Returns true if token belongs to a user and is recent enough
def valid_token(token)
user = User.find_by(authentication_token: params[:token])
user && user.authenticated_at < DateTime.now - 1.day
end
end
I'm using Janrain to handle user sessions in my Ruby on Rails app. It appears to be working, however, I don't know how to tell if a user is logged in or not or access the current user's information. After the user signs in, is there a session variable created?
Assuming you are referring to Janrain Social Login(Engage), once the user authenticates through a Social Provider the widget gets a Janrain OAuth token that is valid for 60 minutes. You can use that token to retrieve the user's profile data through this API end point: (https://{your-engage-domain.com}/api/v2/auth_info).
Janrain Social Login does not maintain any log in state related session data. It simply facilitates authentication and normalizes the retrieval of user profile data from multiple authentication providers. Once a successful authentication event happens it is up to your server to validate the authentication token and then establish any form of authorization session related work.
Most Social Providers return access tokens that are valid for 30-60 days.
try 'current_user' variable, it works in most of the rails authentication libs, e.g.:
#in the erb file:
<% current_user = session[:user_id] %>
# or in the rb file:
class MusicController < ApplicationController
before_filter :authenticate_user! # just like devise
def index
# same methods and api as devise.
return if signed_in? and current_user.email
end
end
# put this method in application_controller.rb
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
more details refer to this example: https://github.com/hatem/janrain-engage-demo