How best to store user credentials in Rails - ruby-on-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?

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 to use Google Oauth2 for both signing in users and creating new user accounts in a rails app?

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.

How to setup a remote json API for Rails for authentication and session

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

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?

Accessing current user information with Janrain and Ruby on Rails

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

Resources