Best way to test user authentication for APIs? - ruby-on-rails

I'm building an API with Ruby on Rails and (attempting to) build user auth with a gem called Devise Token Auth. But I'm uncertain about the best way to validate / authenticate a user before each controller action.
I know of the before_action :authenitcate_user! call but it thinks my users aren't signed in since I'm making calls from an external client.
Do I need to pass other arguments along with my requests that I'm not thinking of? Like a token or session id?

try simple_token_authentication https://github.com/gonzalo-bulnes/simple_token_authentication works with Devise and is easy to integrate.
You just need to send the user authentication token and email, to perform the authentication.

If you are building a lightweight API app I would consider using Knock.
Devise is a great solution for traditional apps where you need a full authentication solution with signups, password editing etc. But it is designed around session based authentication and the codebase is somewhat of a monster due to the crazy amount of customizations available - with token based auth you will only be using about 1% of it.
A basic setup would be:
class ApplicationController < ActionController::API
include Knock::Authenticable
prepend_before_action :authenticate_user
end
Now if we send a request without a valid Authorization: Bearer SOME_TOKEN header Knock will return a 401 - Unauthorized response.
We can test it like so:
class UsersControllerTest < ActionDispatch::IntegrationTest
def token
Knock::AuthToken.new(payload: { sub: users(:one).id }).token
end
it 'does not allow an unauthenicated request' do
get users_path
assert_response :unauthorized
end
it 'allow an authenicated request' do
get users_path, headers: { authorization: "Bearer #{token}" }
assert_response :success
end
end

Related

How can I test authenticated APIs created using Rails 5?

I have an API that requires authentication, created using Rails 5. The basic flow of the authentication is that the user performs a login with a username/password in a Base64-encoded Authorization: Basic header, along with an API key. This is then exchanged for an authorization token, which is recorded in the user database table and is good for some period of time. Subsequent API calls require this token in an Authorization: Bearer header.
The problem I'm having is that, when I try to test a controller that requires authentication, I'm having to go through this dance of logging the user in (to ensure that the auth_token is in the test database table, since this might be the first test that's being run, etc...) This is complicated, because, if, for example, I am testing a controller called RecipesController, and my authentication lives in AuthController, I need to switch controllers in order to perform the login stuff.
I've successfully done this in the past in spec_helper.rb using something like:
def login username, password
current_controller = #controller
... setup login call ...
post :login
#controller = current_controller
... return auth token ...
end
However, as I've realized in Why are parameters not being passed within my test in Rails 5?, I believe this is messing up my test request, and parameters are being lost as a result.
This seems like a pretty straightforward pattern to use, though, so I'm wondering how to test it? I'd actually prefer to test the authentication separately, and just pass in a mocked user object, but I'm not sure how to do this, since I'm not as familiar with Rails as I'd like to be.
Have your Auth verifying function in ApplicationController(assuming your Recipes inheriting from this)
def current_user
return nil unless auth_token
User.find(decoded_token['user_id'])
end
def authenticate_with_token
head(:unauthorized) unless user_signed_in?
end
private
def user_signed_in?
current_user.present?
end
def auth_token
return nil unless request.headers['Authorization'].present?
request.headers['Authorization']&.split(' ')&.last
end
def decoded_token
JsonWebToken.decode(auth_token) #use your own decoder class
end
You can then add before_action :authenticate_with_token on the actions you require authentication.
For tests you can add a helper to login the user so you don't repeat in all places you require auth.
module LoginSupport
def login_user(user:, password:)
valid_credentials = { "email": user.email, password: password}
post '/auth/sessions', params: valid_credentials
valid_jwt_token = JSON.parse(response.body)["token"]
{ "Authorization": "Bearer #{valid_jwt_token}" }.merge(json_api_headers)
end
def json_api_headers
{'Accept' => JSONAPI::MEDIA_TYPE, 'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE}
end
end
RSpec.configure do |config|
config.include LoginSupport
end
Then use the returned Auth token in your request in RecipesContoller tests or any other place.

How best to store user credentials in Rails

I have previously used a session variable, i.e session[:user_id] to keep track of the current user, but I'm now trying to make my app work with EmberJS necessitating a Grape API backend instead of controllers and such. I was wondering, what is the best way to keep track of user credentials across pages: Session, Cookie, or Thread? I'm leaning toward Thread at the moment, but I was wondering what the pros and cons of each are?
Authentication in API's is a little different. The user should be authorized on every request by passing some type of token rather than once per session.
Typically, you'll have a route that accepts username/password that will return an auth token and then the token will be passed as part of BasicAuth or in headers on every request. In Grape, there are a few ways to do this. You can add an Authentication helper:
module ApiHelpers
module Authentication
def authenticate!
error!('401 Unauthorized', 401) unless current_user
end
def current_user
if token_from_headers
#current_user ||= User.find_by api_key: token_from_headers[1]
end
end
def authenticate!
error!('401 Unauthorized', 401) unless current_user
end
def token_from_headers
/Token token="(.+)"/.match(headers['Authorization'])
end
end
end
Include that in the main api file:
class API < Grape::API
helpers ApiHelpers::Authentication
end
Then, for every request that needs authentication:
resource :items do
get do
authenticate!
present current_user.items, with: Entities::Item
end
end
You can also add a custom middleware for authentication or authenticate using basic auth. More info on that in Grape's README: https://github.com/intridea/grape#authentication
I'm assuming your endpoint is using SSL?

Validate client app instead of access_token on some actions in doorkeeper?

I'm using Rails JSON API + Doorkeeper, so I want to let my mobile app that has client_id and client_secrete to request POST /users to create a user without validating with access token. Currently, I create user directly in rails console, and mobile application uses Using Resource Owner Password Credentials flow to get token to access protected resources via API. In this code below, mobile application has to provide access token to be able to invoke index action:
class UsersController < ApplicationController
doorkeeper_for :all, except: :create
def index
...
end
def create
end
end
Currently, create action doesn't validate anything, so is there anyway to tell door_keeper to validate client_id and client_secret before allowing to invoke a particular action? I don't want it to be public.

Is this Rails JSON authentication API (using Devise) secure?

My Rails app uses Devise for authentication. It has a sister iOS app, and users can log in to the iOS app using the same credentials that they use for the web app. So I need some kind of API for authentication.
Lots of similar questions on here point to this tutorial, but it seems to be out-of-date, as the token_authenticatable module has since been removed from Devise and some of the lines throw errors. (I'm using Devise 3.2.2.) I've attempted to roll my own based on that tutorial (and this one), but I'm not 100% confident in it - I feel like there may be something I've misunderstood or missed.
Firstly, following the advice of this gist, I added an authentication_token text attribute to my users table, and the following to user.rb:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Then I have the following controllers:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Note that my application_controller has the line before_filter :authenticate_user!.)
api/sessions_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api/registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
And in config/routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
I'm out of my depth a bit and I'm sure there's something here that my future self will look back on and cringe (there usually is). Some iffy parts:
Firstly, you'll notice that Api::SessionsController inherits from Devise::RegistrationsController whereas Api::RegistrationsController inherits from ApiController (I also have some other controllers such as Api::EventsController < ApiController which deal with more standard REST stuff for my other models and don't have much contact with Devise.) This is a pretty ugly arrangement, but I couldn't figure out another way of getting access the methods I need in Api::RegistrationsController. The tutorial I linked to above has the line include Devise::Controllers::InternalHelpers, but this module seems to have been removed in more recent versions of Devise.
Secondly, I've disabled CSRF protection with the line skip_before_filter :verify_authentication_token. I have my doubts about whether this is a good idea - I see a lot of conflicting or hard to understand advice about whether JSON APIs are vulnerable to CSRF attacks - but adding that line was the only way I could get the damn thing to work.
Thirdly, I want to make sure I understand how authentication works once a user has signed in. Say I have an API call GET /api/friends which returns a list of the current user's friends. As I understand it, the iOS app would have to get the user's authentication_token from the database (which is a fixed value for each user that never changes??), then submit it as a param along with every request, e.g. GET /api/friends?authentication_token=abcdefgh1234, then my Api::FriendsController could do something like User.find_by(authentication_token: params[:authentication_token]) to get the current_user. Is it really this simple, or am I missing something?
So for anyone who's managed to read all the way to the end of this mammoth question, thanks for your time! To summarise:
Is this login system secure? Or is there something I've overlooked or misunderstood, e.g. when it comes to CSRF attacks?
Is my understanding of how to authenticate requests once users are signed in correct? (See "thirdly..." above.)
Is there any way this code can be cleaned up or made nicer? Particularly the ugly design of having one controller inherit from Devise::RegistrationsController and the others from ApiController.
Thanks!
You don't want to disable CSRF, I have read that people think it doesn't apply to JSON APIs for some reason, but this is a misunderstanding. To keep it enabled, you want to make a few changes:
on there server side add a after_filter to your sessions controller:
after_filter :set_csrf_header, only: [:new, :create]
protected
def set_csrf_header
response.headers['X-CSRF-Token'] = form_authenticity_token
end
This will generate a token, put it in your session and copy it in the response header for selected actions.
client side (iOS) you need to make sure two things are in place.
your client needs to scan all server responses for this header and retain it when it is passed along.
... get ahold of response object
// response may be a NSURLResponse object, so convert:
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
// grab token if present, make sure you have a config object to store it in
NSString *token = [[httpResponse allHeaderFields] objectForKey:#"X-CSRF-Token"];
if (token)
[yourConfig setCsrfToken:token];
finally, your client needs to add this token to all 'non GET' requests it sends out:
... get ahold of your request object
if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:#"GET"])
[request setValue:yourConfig.csrfToken forHTTPHeaderField:#"X-CSRF-Token"];
Final piece of the puzzle is to understand that when logging in to devise, two subsequent sessions/csrf tokens are being used. A login flow would look like this:
GET /users/sign_in ->
// new action is called, initial token is set
// now send login form on callback:
POST /users/sign_in <username, password> ->
// create action called, token is reset
// when login is successful, session and token are replaced
// and you can send authenticated requests
Your example seems to mimic the code from the Devise blog - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
As mentioned in that post, you are doing it similar to option 1, which they say is the insecure option. I think the key is that you don't want to simply reset the authentication token every time the user is saved. I think the token should be created explicitly (by some kind of TokenController in the API) and should expire periodically.
You'll notice I say 'I think' since (as far as I can tell) nobody has any more information on this.
The top 10 most common vulnerablites in web applications are documented in the OWASP Top 10. This question mentioned that Cross-Site Request Forgery(CSRF) protection was disabled, and CSRF is on the OWASDP Top 10. In short, CSRF is used by attackers to perform actions as an authenticated user. Disabling CSRF protection will lead to high risk vulnerabilities in an application, and undermines the purpose of having a secure authentication system. Its likely that the CSRF protection was failing, because the client is failing to pass the CSRF synchronization token.
Read the entire OWASP top 10, failing to do so is extremely hazardous. Pay close attention to Broken Authentication and Session Management, also check out the Session Management Cheat Sheet.

Does Devise cach authentication data?

I ran into this weird situation here. I developed a JavaScript-based frontend for my Rails backend API.
I have a login form that posts username and password to my TokensController that returns the authentication_token stored in the database. This token is being stored in a cookie and submitted with every form.
Now I wanted to implement a logout function. So, I delete the cookie so no token gets submitted in a request. (Or a wrong one, in this case the header is Authentication: Token token="undefined")
But still, the Rails backend returns 200 OK with all the data, although the wrong token is defined. How is this possible? Is there any other session cache that is used to authenticate a request?
This is my super class that implements the authentication:
module Api
class SecureBaseController < ApplicationController
prepend_before_filter :get_auth_token
before_filter :authenticate_user!
protected
def get_auth_token
puts token_value
params[:auth_token] = token_value
end
def token_value
if header && header =~ /^Token token="(.+)"$/
$~[1]
end
end
def header
request.headers["Authorization"]
end
end
end
Even puts prints "undefined" in the console, as submitted by the web application, yet it authenticates the user correctly?

Resources