Using the new Devise for authentication for a mobile app - ruby-on-rails

I am trying to implement user authentication using Devise for my Rails/iOS app. I am having trouble since I've mostly been a "user" of Devise and was using it for pure web apps so didn't need to bother so much with what goes on behind the scenes. Now that I have to build authentication for an API based app, it's entirely a different world.
I've read every single tutorial on the web that deals with this topic (most of them are outdated due to the fact that token_authenticatable has been deprecated) but still having trouble understanding what I need to do.
I also read the original GitHub gist talking about this issue and still don't understand what they are talking about.
I'm sure there are people out there just like me who've been just a "user" of Devise so don't really know what goes on behind the scenes.
Can anyone provide a concise solution to implementing an API based authentication system for a mobile app? I mean it can't be that complex, Devise used to be so awesome since all i needed to do was run rails generate, but this has been nightmare for me.

I am working on same things as you want,
for this you have to use token authentication rather than simple
Devise, add following gem in gemfile
# Use device for authentication
gem 'devise'
gem 'simple_token_authentication'
follow documention simple_token_authentication
Use Api like this
curl -v https://example.com/users/sign_in -X POST -H "Accept: application/json" -H "Content-Type: application/json" -d '{"user": {"login": "7838712847" ,"password": "8489", "mobile_type": "ios", "mobile_key": "APA91bG6G4UvjeCWvb8mMAH1ZO3I-8FB3nkRPyCHnwZiXgd16HK18GgoV5n7gjJtZNs038iaFGutzdxnhes3WyaXEX52-xmOyvuEK8S1abBdpuhD9AD5bzLWeu-1Ow_yZRTVg3Nypz1z"}}'
I am using mobile number to login so customize gem according your need
please let me know if it is not working (mail me: er.mukeshsharma.rj21#gmail.com)

Here is an approach that works excellent for me, when using Devise for authentication in a Rails app. If tests for a token first (you can set the token by any iOS, Android, ... app) and falls back to the default authentication method for your web users.
Rails
Add your own token to the user model, by adding an :api_token string column and fill that with a unique value per user. Using a Digest::SHA1 of some user data (like id + email) is a good starting point, but you can (and should) go as crazy as you like when it comes to generating a unique token.
Create a method for authentication over that token. You can add it to your main ApplicationController for easy access (don't forget to put the method in your private section of the controller);
def authenticate_user_by_token
#api_token = request.headers['HTTP_AUTHORIZATION']
if #api_token.present? && #user = User.find_by_api_token(#api_token)
sign_in #user
return #user
else
return false
end
end
Next create a (private) method and chain this method to the devise before filter method you are using (like :authenticate_user! for example). Put it in the same controller as the method above for easy access;
def authenticate_by_token_or_devise!
return authenticate_user! unless authenticate_user_by_token
end
Now Replace your current before_filter call from :authenticate_user! to the newly created one; :authenticate_by_token_or_devise!, like so;
before_filter :authenticate_by_token_or_devise!
Or, starting from rails 4 (Rails 4: before_filter vs. before_action), use before_action;
before_action :authenticate_by_token_or_devise!
iOS
Now all you have to do is add that token to your iOS app. Depending on the framework that you use in your app, this might be different then the code below.
I use AFNetworking (https://github.com/AFNetworking/AFNetworking) in this example. This is how you set the Authorisation header token in your AFHTTPRequestOperationManager so it gets added to every request you make.
NSString *apiToken = #"your-token-here";
[[_manager requestSerializer] setValue:apiToken forHTTPHeaderField:#"Authorization"];
Optional
Additionally, you can create a before filter method that allows access to token-based authentication only (e.g. if you have a set of /api routes that you only want to be accessed using the token) like this;
def authenticate_user_by_token!
if !authenticate_user_by_token
render nothing: true, status: :unauthorized and return
end
end

Recently, we also had to set up token based authentication for our webapp (for API access) - and we also stumbled upon the fact that it has been removed from Devise.
We went with Simple Token Authentication which worked just beautifully.

When I recently implemented an API, I grudgingly followed a suggestion to use Warden, a rack-based authentication gem. My sense was that an authentication gem that required you to write your own authentication was broken. But this gem provides just the right level of control. My only complaint is that the gem don't handle POST parameters well. I was able to work around it, but that kind of concern should be (IMO) handled by the gem.
Having used it, I highly recommend this gem for any scenario requiring non-generic authentication. Rolling your own authentication strategies is a joy because (a) it's pretty simple and (b) you aren't bound by other devs' assumptions.
To help you get started, here is my config/initializers/warden.rb file.

You can use a combination of the devise gem and doorkeeper gem to support web and mobile authentication.
For example, I used devise for signing up users and handling forget password and email confirmation flow. For mobile clients, I used the doorkeeper gem as a oauth2 provider to protect my apis. There are many oauth2 grant flows supported by the doorkeeper gem and I suggest you can take a look at those.
Here's a link! to get started

Related

How to validate that a user owns the requested resource through Rails API when using devise_token_auth

I am building an API-only (for now) Rails app to serve as the back end for an Android app I'm building. I was previously using Firebase but wanted to do more processing on application data and I didn't want to bloat the mobile client with all the logic, so I am moving away from Firebase's real-time database and backing the application data using Rails. I was also using Firebase's authentication which is very straightforward. But it seems less complex for the overall system to keep all of this functionality in one place, so I'd like to perform auth and user management in the Rails app as well.
I have installed devise_token_auth (seen here) and finished the basic configuration. The /auth route works correctly if params are provided and creates a user. sign_in and sign_out both successfully create sessions and return some header information. The important parts returned are client, access-token, and uid, which I need to use in future calls to the API. I believe these are invalidated and replaced with each subsequent call. At this part of the flow is where I'm not sure how to proceed. I don't understand how the data in these headers is associated with the user who signed in and how I can validate that they own a resource they request. To summarize the question another way:
How can I sign a user into the API and then validate which user is making subsequent API calls?
My Android app is a task manager, so I need to be able to validate for example that if user 1 requests task 3, that they own that resource. I'm also unsure how to direct index calls using the API. That is, when the index endpoint is hit (/tasks), how can I identify from the data in the API headers which user's tasks I should retrieve.
I haven't been able to find any solid tutorials or samples using devise_token_auth so I'm having trouble stitching together the interaction between the pieces I have now. I know this is a meaty question - thanks in advance for any guidance.
How can I [...] validate which user is making subsequent API calls?
With the current_user method. This is a built-in feature to the devise_token_auth gem.
I need to be able to validate for example that if user 1 requests task 3, that they own that resource
There are many different approaches you could take. You could just write some custom logic in each controller action, using the current_user method (and return 403 forbidden if necessary).
Or, you could use a popular "framework" solution for this such as CanCanCan or Pundit. I, and probably most of the modern community, would recommend Pundit.
I highly advise you to read that library's README, as it's extremely helpful. But for the example above, you could write something like this:
class TasksController
def show
task = Task.find(params[:id])
authorize(task) # !!!
render task
end
end
# app/policies/task_policy.rb
class TaskPolicy
def show?
record.user == user
end
end
(Note that by default, the "user" in Pundit policies calls the method: current_user. This is all explained in the project's README.)
when the index endpoint is hit (/tasks), how can I identify from the data in the API headers which user's tasks I should retrieve
Again, this is all handled as part of Pundit's standard features. You just need to define a TaskPolicy::Scope and call policy_scope(Task) in the controller - as explained here.

How do I authenticate using Devise in a Rails REST API App?

I'm creating a Rails app which have both a GUI part, and a REST/JSON-API.
The REST/JSON API is fairly simple, and the controller returns data like this:
def get_players
#players = Player.all
render json: #players
end
The GUI part of the app is using Devise for authentication, and it works fine.
Now I want to add authentication for the REST/JSON Api too, how do I do that?
Also, how do I test the REST API using curl when the Authentication is added?
---- edit ----
as it turns out, Devise wasnt necessary in this case. A home-cooked token-authentication method works for now. (token created when Player is created, and returned on correct e-mail/password combo).
After getting a few tips, I found a number of great sites. There are several ways to do this, however I don't know which one is best, but these sites help a long way:
https://github.com/lynndylanhurley/devise_token_auth (An extension to
Devise)
https://labs.kollegorna.se/blog/2015/04/build-an-api-now/
(Manual way)
Token based authentication for Rails JSON APIs (SO Question)
Rails API : Best way to implement authentication? (SO Question)
Rails API : Best way to implement authentication?

JSON Web Token with Devise

I hope this does not count as an opinionated question. I just need to be pointed in the right direction.
I am modifying the Devise gem to work purely with JSON. I have had no problems with the registration, confirmation, re-confirmation, locking so far.
However, while working with the sign in, I dug deeper and understand that the default Devise sign in strategy uses Warden as it has to do with sessions and Rack authentication.
I understand JWT contains all the information in itself and does not need sessions.
So if I strip the default Devise strategy of everything and simply return a JWT on success and errors on error, would that be the right approach?
Am I missing something?
In order to use JWT with devise, I recommend to not monkey patch devise and instead use a tool others can audit and test.
For this reason, I developed devise-jwt. It does zero monkey patching and leverages warden, which is the authentication library below devise. You can also read more about it in this post I wrote: A Secure JWT Authentication Implementation for Rack and Rails
Hope it helps
I wouldn't use devise_token_auth since it seems like too much hassle and ... you store tokens in db :/. Why would we want to do so if JWT is available.
I'd rather add a new strategy to Warden/Devise couple and let them work as they should.
Here's an example: https://medium.com/#goncalvesjoao/rails-devise-jwt-and-the-forgotten-warden-67cfcf8a0b73 . One thing to note: JWTWrapper doesn't really belong to app/helpers/ . You need to inject somewhere a call to JWTWrapper.encode({ user_id: current_user.id }) once your users successfully signs in with their email/password. Perhaps in the Devise SessionsController?
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
render json: JWTWrapper.encode({user_id:current_user.id})
end
You might want to do this only for xhr or json (format) requests
You probably shouldn't be hacking your Devise gem source. I suggest to just use Devise Token Auth gem to handle tokens instead.
https://github.com/lynndylanhurley/devise_token_auth
It will generate and authenticate valid RFC 6750 Bearer Tokens.
According to their README.
Seamless integration with both the the venerable ng-token-auth module for angular.js and the outstanding jToker plugin for jQuery.
Oauth2 authentication using OmniAuth.
Email authentication using Devise, including:
User registration
Password reset
Account updates
Account deletion
Support for multiple user models.
It is secure.
Sorry for late answer, but I'm actually working on the same problem, and want to share my opinions on that.
First, I would emphasize not changing Davise sources, this will likely bring you to further problems, especially when Devise code changes.
On the other hand, as I have encountered devise-token-auth, it might not be viable for your needs, especially in distributed systems (SOA). Perhaps I'm wrong, but as I see devise-token-auth, you can't add Subjects to restrict user access solely on the token. If you don't need this feature, you really should try devise-token-auth.
If you want to store additional information in the token, you could try to authenticate against a regular devise or devise-token-auth and then encode your information using a JWT gem.
Example can be found here: https://www.sitepoint.com/introduction-to-using-jwt-in-rails/

Learning Doorkeeper

I am trying to set up APIs for a broker application (users can buy/sell items when they are logged in). Developers should be able to build on top of my app. Aiming to use Devise and Doorkeeper gems. I have already set up Devise (so my users have to log in/log out of their accounts). But I am having problems understanding how to use Doorkeeper gem.
I have read through the following so far
RFC 6749
Doorkeeper wiki
Tutorial for Oauth 2 on Rails (Wasn't very helpful as I got stuck while following the steps)
Oauth with Doorkeeper railscast
Problem is - I still don't quite get how to set up my servers, create pages which other developers can register their app on, attain their keys,etc.
What am I missing, is there any comprehensive tutorial which I can learn from to set up my API for developers to register their app on, login on behalf of users and execute buy/sell orders?
Registering applications
Doorkeeper comes with integrated controllers/views to manage oauth applications, request access tokens and authorizations.
If you installed and configured doorkeeper correctly, these routes are defined in your rails application :
GET /oauth/authorize/:code
GET /oauth/authorize
POST /oauth/authorize
DELETE /oauth/authorize
POST /oauth/token
POST /oauth/revoke
resources /oauth/applications
GET /oauth/authorized_applications
DELETE /oauth/authorized_applications/:id
GET /oauth/token/info
(See https://github.com/doorkeeper-gem/doorkeeper#routes)
When you go to /oauth/applications, you can add or remove oauth applications. This might help for your problem
create pages which other developers can register their app on, attain their keys
However, these are made for backend or quick setup purposes. It's not recommanded to use that in production. You can create controllers/views based on these to start with.
If you want to learn more about customizing these controllers/views, check these links :
https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes
https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-views
Setting up your API
If you don't use the Rails API mode, I recommend you use the grape gem, which is a framework for building APIs in Ruby. You can then mount your Grape::API application to a route of your Rails application.
Then, when users will register their applications, they will be asking for access grants (authorization code) for each individual user of their own applications. The /oauth/authorize routes are exactly that. They will use an OAuth2 client to build the authorize_url properly and setup their applications.
All this flow is compliant with the OAuth2 framework (RFC 6749). But as you said; you read it, so you should understand what's going on behind the scene.
All you have to do is provide your API endpoints :
# in your API class that extends Grape::API
post 'orders/:id/buy' do
# authorize a specific scope, this is just an example,
# this might not suit your app design
doorkeeper_authorize! :buy_order
# you can get the resource owner id with and other token infos,
# or put that in a helper method
current_user = User.find(doorkeeper_token.resource_owner_id)
# buy order logic goes here...
end
post 'orders/:id/sell' do
doorkeeper_authorize! :sell_order
current_user = User.find(doorkeeper_token.resource_owner_id)
# sell order logic goes here...
end
Hope that can help!

Rails: Devise Authentication from an ActiveResource call

My two rails applications(app1, app2) are communicating using active resource.
app1 calls app2 create a user inside app2. app2 would create the user and would like app1 then redirect the user to app2's authenticated pages.
going from app1 to app2 would invariably ask the user to log in.
I was looking for a way to avoid this login step in app2, instead make the user log in during the first active resource call to create user, and somehow get the authentication token written.
Authentication is done using Devise. Is there anything built into Devise that support this?
Is passing around the authentication token the way to go?
You are trying to implement a form of Single Sign-On service (SSO) (sign in with app1, and be automatically authenticated with app2, app3...). It is unfortunately not a trivial task. You can probably make it work (maybe you already did), but instead of trying to reinvent the wheel, why not instead integrate an existing solution? Or even better, a standard protocol? It is actually relatively easy.
CAS server
RubyCAS is a Ruby server that implements Yale University's CAS (Central Authentication Service) protocol. I had great success with it.
The tricky part is getting it to work with your existing Devise authentication database. We faced the same problem, and after some code diving, I came up with the following, which works like a charm for us. This goes in your RubyCAS server config, by default /etc/rubycas-server/config.yml. Of course, adapt as necessary:
authenticator:
class: CASServer::Authenticators::SQLEncrypted
database:
adapter: sqlite3
database: /path/to/your/devise/production.sqlite3
user_table: users
username_column: email
password_column: encrypted_password
encrypt_function: 'require "bcrypt"; user.encrypted_password == ::BCrypt::Engine.hash_secret("#{#password}", ::BCrypt::Password.new(user.encrypted_password).salt)'
enter code here
That encrypt_function was pain to figure out... I am not too happy about embedding a require statement in there, but hey, it works. Any improvement would be welcome though.
CAS client(s)
For the client side (module that you will want to integrate into app2, app3...), a Rails plugin is provided by the RubyCAS-client gem.
You will need an initializer rubycas_client.rb, something like:
require 'casclient'
require 'casclient/frameworks/rails/filter'
CASClient::Frameworks::Rails::Filter.configure(
:cas_base_url => "https://cas.example.com/"
)
Finally, you can re-wire a few Devise calls to use CAS so your current code will work almost as-is:
# Mandatory authentication
def authenticate_user!
CASClient::Frameworks::Rails::Filter.filter(self)
end
# Optional authentication (not in Devise)
def authenticate_user
CASClient::Frameworks::Rails::GatewayFilter
end
def user_signed_in?
session[:cas_user].present?
end
Unfortunately there is no direct way to replace current_user, but you can try the suggestions below:
current_user with direct DB access
If your client apps have access to the backend users database, you could load the user data from there:
def current_user
return nil if session[:cas_user].nil?
return User.find_by_email(session[:cas_user])
end
But for a more extensible architecture, you may want to keep the apps separate from the backend. For the, you can try the following two methods.
current_user using CAS extra_attributes
Use the extra_attributes provided by the CAS protocol: basically, pass all the necessary user data as extra_attributes in the CAS token (add an extra_attributes key, listing the needed attributes, to your authenticator in config.yml), and rebuild a virtual user on the client side. The code would look something like this:
def current_user
return nil if session[:cas_user].nil?
email = session[:cas_user]
extra_attributes = session[:cas_extra_attributes]
user = VirtualUser.new(:email => email,
:name => extra_attributes[:name],
:mojo => extra_attributes[:mojo],
)
return user
end
The VirtualUser class definition is left as an exercise. Hint: using a tableless ActiveRecord (see Railscast #193) should let you write a drop-in replacement that should just work as-is with your existing code.
current_user using an XML API on the backend and an ActiveResource
Another possibility is to prepare an XML API on the users backend, then use an ActiveResource to retrieve your User model. In that case, assuming your XML API accepts an email parameter to filter the users list, the code would look like:
def current_user
return nil if session[:cas_user].nil?
email = session[:cas_user]
# Here User is an ActiveResource
return User.all(:params => {:email => email}).first
end
While this method requires an extra request, we found it to be the most flexible. Just be sure to secure your XML API or you may be opening a gapping security hole in your system. SSL, HTTP authentication, and since it is for internal use only, throw in IP restrictions for good measure.
Bonus: other frameworks can join the fun too!
Since CAS is a standard protocol, you get the added benefit of allowing apps using other technologies to use your Single Sign-On service. There are official clients for Java, PHP, .Net and Apache.
Let me know if this was of any help, and don't hesitate to ask if you have any question.

Resources