OmniAuth dynamic callback url to authenticate particular objects instead of current_user - omniauth

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']

Related

Rails route namespaced in omniauth

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.

rails access signed in user and the user id for the profile for a multi-page profile

Hi clever programmers,
I've been searching and reading a couple days, but I need some Rails help-
Here is my goal: I want to make a multi-page profile for each user, and I'd like to handle this profile with a profile controller.
The problem? How do I make the 'show' REST action apply to an entire controller instead of just one page? Specifically, how can I have both a #current_user and a #user variable available in the Profile controller that correspond to the signed-in user and the current user's page.
I'm not sure if I should be making routes with multiple :id s in the route or if there is some way to persist the signed-in-user in something like #current_user when they sign in that is just available everywhere and then I would use the :id of the user who's profile it is in the route. I'm pretty sure facebook does something like facebook.com/{your_id}/{their_id}/ for example.
I tried accessing #current_user from my session_helper.rb class but it came up nil and I'm not sure how to pass the :id to use User.find(params[:id]) because the profile controller is not affiliated with the resource for the User model.
Any protips or links to helpful readings would be much appreciated. I'm a beginner so feel free to suggest a better course of action if I'm going against the rails way. Thanks in advance!
You may not know but you can store session data, and user_id it's really common thing that people saves on it.
So for saving at the sign in
session[user_id] = ....
Then you could have something like this on a helper
def current_user
User.find(session[user_id]) if session[user_id]
end
You should check the gem called devise. It provides all the functionality for aunthentication and it also provides a current_user method everywhere. You should check it at least to see how they have implemented that method.

Rails 3: How Does One Restrict Access to IDs

This might be very simple; I don't know Rails very well.
I have a match myController/myAction/myID in my routes.rb that will direct hyperlinks to the proper page (using link_to). But here's the problem: I don't want people to be able to freely modify the id parameter, passing in via the URL whatever they like.
Is there a way to perhaps restrict access to routes to the link_to method only? Or maybe there's another way to go about this, using a passed in hidden variable param or something?
Users access you site via urls like: /controller/action/:id right? A user can change an id and must not view another non authorized resource. How to achieve this?, on your controller, return only those resources that user is allowed to access.
For example, suppose that you are using devise:
class AController < ApplicationController
def index
#resouces = current_user.find_all_by_id params[:id]
end
end
This way if the user tries to access something he does not have access to, he will get an error.
Hope this helps, if not please let me know and I'll try to elaborate.
About current_user, yes it is supposed to be the current logged in user, it doesn't have to be devise, you can implement your own session handling logic and then create a helper method to retrieve the currently logged in user.
About using devise, if you don't want to implement your own session handling logic, plus if you want features like:
remember me
already created views that you can fully customize
authentication
authorization
password encryption
many more (please look at the docs for further information)
Then devise is a good way to go.
Also, it is always a great idea, if possible and as a learning exercise, implement your own authentication and authorization layers, you won't regret.
Best regards
Emmanuel Delgado

Should I use an authorization gem for this or a simple before_filter method instead?

I have two types of users: user and editor. I have a User model with the boolean column is_editor to determine if a user is an editor.
Let's assume. User Foobar decides to sign up as an editor. He succeeds. From today onwards, he is an editor. One day Foobar accidentally navigates to the editor registration page (registrations controller, new action).
Since Foobar is already an Editor, I should redirect him to his profile page. Should I use an authorization gem (such as Cancan) for this? Or should I have a simple method (i.e. before_filter :check_if_user_is_not_an_editor) in the registrations controller that checks if user is already an editor and redirect?
If I end up using the Cancan approach. The thing is, I already have the following that checks for other authorization.
rescue_from CanCan::AccessDenied do |exception|
flash[:alert] = exception.message
redirect_to root_url
end
Which will render a flash alert message: You are not authorized and redirect to root url. Which is not what I want, because I need to redirect to Foobar's profile instead.
What are your thoughts? Is this the task of authorization or just a simple redirect in the said controller? Which is the more appropriate approach?
Honestly, it seems pretty minor so whatever you choose, I wouldn't feel bad about your approach. Personally, I would go with your second option (simple redirect). First of all, it seems simpler, which is always a plus. If you're using an authentication solution like Devise, you probably have a current_user or user_signed_in? helper that you can use in a before filter quite easily. Secondly, it doesn't really strike me as the type of problem that authorization is concerned with.
In one sense, it is a permissions concern (I guess semantically anyways) since your application defines behavior that is 'not allowed'. Realistically, it's not allowed but not because the user doesn't have necessary permission. The reason the behavior isn't allowed is because no user should be able to register as the same type of user they're already registered as - that is, there are no user types that should be able to do such a thing so the currently-logged in user's permissions are moot. It seems like your problem should be resolved by defining application behavior - not user permissions.
Just the way I see things, feel free to implement whatever solution you feel fits best.

Mocks aren't working with RSpec and Devise

I'm working on a Rails 3 web app at the moment with RSpec 2 and we're using Devise for authentication. One (and soon many) of our controllers require the user to be logged in. I know Devise provides the sign_in test helper, but can it be used with an RSpec or Mocha mock object?
I originally tried #user = mock_model(User) where user is the Devise class. This wouldn't work with sign_in :user, #user as get 'index' would redirect to the sign in form.
Does anyone have any experience testing with Devise and can help?
We had a similar problem, but using Factory Girl. We solved it like so:
In spec_helper.rb:
config.include Devise::TestHelpers, :type => :controller
In the controller spec (just a wrapper method):
def login_user(user)
sign_in user
end
Then in each method you require, you can do:
login_user(Factory(:user))
... where you have defined a user object in factories.rb. Not sure if this will work with mocks though.
A mock is never going to work. When you say sign in, the user is stored in session (basically, the user class and its id). When you access the controller, another user object is retrieved based on the stored data. The best way to solve the problem is using something that persists the object, like Factory Girl.
I hit the same issue. I'm doing the following for now:
before(:each) do
# sign_in mock_user
request.env['warden'] = mock(Warden, :authenticate => mock_user,
:authenticate! => mock_user)
end
I've created an issue for this here: https://github.com/plataformatec/devise/issues#issue/928
Go vote!
None of them worked for me (MRI 1.9.3-preview1, rails 3.0.1.rc5).
this is the solution i found : http://blog.joshmcarthur.com/post/6407481655/integration-tests-with-devise-and-rspec