I have this in routes.rb:
namespace :api do
namespace :v1 do
...
devise_for :users, constraints: { format: :json },
:controllers => { :omniauth_callbacks => "auths" }
...
end
end
And produces among others, these routes:
new_api_v1_user_confirmation GET /api/v1/users/confirmation/new(.:format) api/v1/confirmations#new {:format=>:json}
GET /api/v1/users/confirmation(.:format) api/v1/confirmations#show {:format=>:json}
api_v1_user_omniauth_authorize /users/auth/:provider(.:format) auths#passthru {:provider=>/facebook|twitter|linkedin/, :format=>:json}
api_v1_user_omniauth_callback /users/auth/:action/callback(.:format) auths#(?-mix:facebook|twitter|linkedin) {:format=>:json}
How could a get last two routes namespaced, something like:
/api/v1/auth/:provider(.:format)
/api/v1/auth/:provider/callback(.:format)
Guess I should convert my comments into an answer:
For our app we are doing the pure json api thing, with backbone/marionette. To get oAuth working with devise - I removed it from devise. :) Removed the omniauthable property I had set up and removed the omniauth settings from my initializers/devise.rb. Then reading on the omniauth page I implemented it by itself.
My api lives under "/api/v1"
Created the initializers/omniauth.rb file listing my providers and keys. For each provider I also gave it a :path_prefix=>"/api/v1/auth" property.
Create a callback controller within my api called api/v1/oauth_controller.rb This was properly namespaced with modules and contains my callback path from the services.
Updated my routes to setup the callback route for omni. See here: gist.github.com/DaveSanders/5835642
Within OAuthController.create I consumed the details from the provider and go through the basic flow of "does the social network user exist and have a mapped account?" if so, log them in via devise's user.sign_in? If not, create the user and then sign them in.
Redirect back to my app, which then boots up backbone again, which can then go get the logged in user details and use them as needed.
Your implementation may vary, but the way I handle my oAuth accounts is put them in their own tables (Twitters, Facebooks, etc) and then link them into my devise user. This way I can have multiple accounts associated and the user can log in with any of them.
Also, be sure to set your twitter/facebook callback to something like:
http://127.0.0.1:3000/api/v1/auth/twitter/callback
to match your route in dev.
Hope this helps others. If I forgot a step or you get lost, please ask.
Related
I want to protect the ip address and url of rails app
xxx.xxx.xxx.xxx/controller/action
are accessible, i want to protect these urls and controller actions.
You can always add a before filter like mentioned below
class CommentsController < ApplicationController
http_basic_authenticate_with name: "foo", password: "secret", only: :name_of_action_you_want_to_restrict
def some method
...
end
end
In this manner you will have a basic authentication for your pages you want to have.
Also if you want to restrict access at a higher level, you will have to go with rails authentication using users and sessions controller.
Hope this helps
Cheers !
I am working on an app where a company admin should be able to create and update users in his company. (Users can also create and update their accounts independently).
I used Devise and Cancan for registration and permission management. I configured Devise to get the required signup and user update processes. I created a namespace for the admin views.
My admin controller (/app/controllers/admin/base_controller.rb) looks like this:
class Admin::BaseController < ApplicationController
authorize_resource :class => false
layout 'admin'
def dashboard
end
end
In addition to my "regular" users_controller, I have a controller (/app/controllers/admin/users_controller.rb) and associated views dedicated to Admin user management.
class Admin::UsersController < Admin::BaseController
...
end
Now, what's the cleanest way to implement Devise-related user admin features (at this point, create and update users)?
Have a conditional (based on user permissions) in my "regular" registrations_controller and confirmations_controller to render and redirect different views? (cf. Devise form within a different controller)
Or create new controllers in my admin namespace? If the latest is better, what are the main steps to follow?
Herein lies your issue, once you namespace or move any user management to another controller, you leave the scope of Devise. So at the point where you are in Admin::UsersController, Devise doesn't care about what you do, there are no 'devise-related' admin features as you stated. You can implement your Admin::UsersController as a standard RESTful controller if you wish.
In this manner, creating users through a namespaced controller, Devise will still perform actions such as confirmations. There is one small thing to keep in mind when creating and updating users this way. If you do not set a password as the admin, you will have to delete the password and password_confirmation from the params hash. To do so, the start of your create and update actions would look like so:
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
I employ this same method in many of my applications, and have yet to have it fail.
EDIT
namespace :admin do
resources :users
end
Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.
Say I have the models User and Project. Users and projects are HABTM-associated. My setup is actually a bit more complicated than this, but I think for the purposes of my question this will do.
Now, I want to use omniauth to authenticate a particular project with Twitter, Facebook, what have you. I've figured out how to define my omniauth path_prefix, but I don't know how I could pass in a variable like so: config.path_prefix = 'projects/:project_id/auth', much less make a custom callback url like project/:project_id/auth/twitter/callback.
This will break in production. In development you can get away with a session variable. But in production you need to have the callback url contain your project_id as it could be 2 or more register with different auth_project_id's and then you have no way of knowing which one is called afterwards (the callback is asynchronous).
https://github.com/mkdynamic/omniauth-facebook#custom-callback-urlpath
something like config.path_prefix = "projects/#{#project.id}/auth" might work. I'm testing a similar situation right now.
For posterity's sake, I solved it this way:
I added an auth method to my projects controller, which set a session variable session[:auth_project_id] and then redirectes to auth/ + params[:provider].
In my callback controller authentications, I got my project with #project = Project.find(session[:auth_project_id]), created the authentication, and then session[:auth_project_id] = nil to unset the session variable.
I have done similar thing with devise omniauthable, You can pas any parameter with link. like
<%= link_to "Add twitter Account", user_omniauth_authorize_path(:twitter, params: { project_id: #project.id }) %>
Then in your callback controller
before_action :set_project, only: [:twitter]
def set_project
#project = Project.find(request.env['omniauth.params']['project_id'])
end
Note: Do NOT use request.env['omniauth.auth']
I'm creating a new Rails 3 app and I want to allow users to sign-in using their Facebook or Twitter credentials.
I don't know whether I should implement this using Devise and OmniAuth, or just OmniAuth. I just watched Ryan Bate's screencast on Simple OmniAuth and it seems like I could just use OmniAuth, but I'm not sure it's enough.
I have the following requirements:
Allow sign-in via Facebook and/or Twitter. I will not be implementing local user accounts/passwords.
Signing in via FB/Twitter for the first time should create a new user in the db so I can store the associated FB/Twitter oAuth tokens.
Users should be able to associate both a FB and a Twitter acct to their profile/user so they can post to both FB and Twitter.
Users should be able to delete their account.
I posted this on the Devise Google Mailing List and got this response from José Valim (Devise maintainer and Rails core team member):
"You can use just OmniAuth. If you use Devise, the only benefit is that it will add Omniauth url helpers, but that is so minimal that honestly is not worth the overhead."
-- José Valim
Devise is a fancy way to automatically handle all the things that go with user accounts. If you don't need all the bells and whistles, you should definitely just go the Simple OmniAuth way like in the screencast.
The only hitch I see with trying to link up a Facebook and Twitter account is that you'll have to require them to be signed into one in order to link the other -- and if they do happen to sign in on separate occasions, you could possibly have two Users in your database. This wouldn't be a problem if you were doing Google and Facebook because they both send back an email address, but Twitter only sends back a username, no email address.
You'll have to add a field to the User model for a username (Twitter) and email address (Facebook) so you can attempt to link the accounts if a visitor did it separately and wants to link them later. Just be careful of that when you set it up.
If you can't use just OmniAuth, maybe because, like in my case, you want to use ActiveAdmin which depends on Devise and methods like current_user would conflict, you can just override the login page with your own:
match '/users/sign_in', :to => "sessions#new"
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
I have a "Sign in with Facebook" link in my nav, but this page is necessary for redirecting users when they're trying to access a protected page while not logged in.
Edit: Actually, there is a section on the wiki page: "Using OmniAuth without other authentications"