Does Devise cach authentication data? - ruby-on-rails

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?

Related

Devise + JWT Not getting Correct User, after logging out from first user

So my Problem is : When i sign in with User A(is Admin) it works fine and i can list the List of User in the database.
The problem occurs when i logout and sign in with user B(regular user)I can list the users as well which shouldn't happen (and current_user is still User A). BUT if i delete the httpOnly cookie that the rails application sends back and then request the list of user with the token from JWT with user B, I get 401 which is what i want.
I have an initializer
module Devise
module Strategies
class JWTAuthenticatable < Base
def authenticate!
token = get_token
return fail(:invalid) unless token.present?
payload = WebToken.decode(token)
return fail(:invalid) if payload == :expire
resource = mapping.to.find(payload['user_id'])
return fail(:not_found_in_database) unless resource
success! resource
end
private
def get_token
auth_header.present? && auth_header.split(' ').last
end
def auth_header
request.headers['Authorization']
end
end
end
end
And My application controller is
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session, prepend: true
before_action :authenticate_user!
end
Which only gets hit once from the first request but never any requests after that, and current_user stays stuck on the first signed in user. But again when the httpOnly cookie is deleted that Module gets hit and set current_user to User B.
I send JWT with every request but funny thing is when deleted after the first request i still get a response as if JWT exists.
Hope im explaining this correctly
Ive been searching for answers for about a week now. Any help would be appreciated
Found an answer in another post- Had to override devises store? to return false instead Solution Here

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.

Issues with Rails API mode with Clearance gem?

I have followed the steps here https://github.com/thoughtbot/clearance/wiki/API-Authentication (inserted below) to get my Rails API only app going with authentication.
I have run into a couple issues. The first being that "cookies" is undefined.So I commented that out.
Now I am getting
NameError (undefined local variable or method 'form_authenticity_token' for #<BookmakersController:0x00007ffa6f370c78>):
app/controllers/application_controller.rb:12:in `authenticate_via_token'
I can't seem to resolve this last one. BookmakersController is one of my controllers obviously where I have before_action :authenticate_via_token
I am using Postman with Authorization headers set to send a get request to my app.
Any ideas how I can get through this error?
class ApplicationController
protected
def authenticate_via_token
return unless api_token
user = User.find_by_api_token(api_token)
sign_in user if user
cookies.delete(:remember_token) # so non-browser clients don't act like browsers and persist sessions in cookies
end
private
def api_token
pattern = /^Bearer /
header = request.env["HTTP_AUTHORIZATION"]
header.gsub(pattern, '') if header && header.match(pattern)
end
end
class MyController < ApplicationController
before_action :authenticate_via_token
end

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?

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

Resources