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
Related
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
I am using ng-auth-token and devise_token_auth for authentication which is working fine. I am able to login from front end but when i visit an API url directly in browser it doesnt show any current_user. What i want to do is i want to integrate paypal checkout, so when i come back from paypal to my app after user authorization, current_user is nil and also session variable is empty (even if i set some session variable before going to paypal site).
If i add
before_action :authenticate_user!
it gives me
Filter chain halted as :authenticate_user! rendered or redirected
even if i am logged in.
I don't know how can i handle these callback response from other apps.
I found a workaround to this, but still waiting for a proper solution.
# In ApplicationController
def authenticate_current_user
head :unauthorized if get_current_user.nil?
end
def get_current_user
return nil unless cookies[:auth_headers]
auth_headers = JSON.parse cookies[:auth_headers]
expiration_datetime = DateTime.strptime(auth_headers["expiry"], "%s")
current_user = User.find_by(uid: auth_headers["uid"])
if current_user &&
current_user.tokens.has_key?(auth_headers["client"]) &&
expiration_datetime > DateTime.now
#current_user = current_user
end
#current_user
end
and use this in controllers
# In any controllers
before_action :authenticate_current_user
source: https://github.com/lynndylanhurley/devise_token_auth/issues/74
Thanks.
Addon to Ankit's solution (rep too low to comment):
This was failing for me on post requests because Rails was stripping out the cookies due to protect_from_forgery being set:
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
include Pundit
protect_from_forgery with: :null_session # <-- this was the culprit
end
Removing protect_from_forgery entirely "solved" the issue, though I'm not happy with it.
The real issue (on my end, at least) is that ng-token-auth is supposed to be including the token in the header, but is only found in the cookies. My current guess is that either 1) ng-token-auth isn't properly setting its HttpInterceptor, or 2) some other interceptor is messing with it after the fact. (I've seen the ng-file-upload can cause issues, but I'm not using that...)
I have ended up with this code in ApplicationController:
before_action :merge_auth_headers
def merge_auth_headers
if auth_headers = cookies[:auth_headers]
request.headers.merge!(JSON.parse(auth_headers))
end
end
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
I am developing a rails application, that allows optional HTTP basic authentication.
Authorization should be allowed, but not mandatory.
To do this, I am trying to use a before_action inside the application controller, that will try to find a user matching the given credentials and either write that user or nil into a global variable.
I tried this, but the block for authenticate_with_http_basic doesn't seem to be called at all (The console doesn't show the username and password, that I supplied, however logging outside of the block works):
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate
def authenticate
authenticate_with_http_basic do |username, password|
logger.info "Login:"+username+" "+password
#auth_user = User.authenticate(username, password)
end
end
end
And I tried this:
def authenticate
if user = authenticate_with_http_basic { |username, password| User.authenticate(username, password) }
#auth_user = user
end
end
I also tried this, which throws an error undefined method 'split' for nil:NilClass. When looking at the documentation, I see that split is being called on part of the request. Am I doing something wrong with just assuming the request variable should be accessible from a before_action inside the application controller?
def authenticate
username, password = ActionController::HttpAuthentication::Basic::user_name_and_password(request);
logger.info "Login:"+username+" "+password
#auth_user = User.authenticate(username, password)
end
I just need a simple function that gives me username and password as string variables. What am I doing wrong? Is there another way to accomplish that seemingly simple functionality?
Update
The things I tried seem to work. My only mistake was to use a regular webbrowser to debug my API. Most web browsers don't send authorization to the server, before they get a www-authenticate header back, even if the user explicitly included it in the URL.
As long as it is just used as an API or accessed through other ways, this should not be a limitation. However, this kind of optional authorization, that does not present an authorization dialog doesn't work with regular browsers (at least not as a HTTP authorization). It is not a problem with Rails, just the way browsers are built.
you might just be using the wrong method. this is one of the examples from ApiDock:
class AdminController < ApplicationController
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic('Administration') do |username, password|
username == 'admin' && password == 'password'
end
end
end
see this question for more details: In Ruby on Rails, what does authenticate_with_http_basic do?
UPDATE
i don't see any problems without requesting basic auth. it works as expected:
class HomeController < ApplicationController
before_action :authenticate
private
def authenticate
authenticate_with_http_basic do |username, password|
logger.info "try basic-auth without requesting it: username=#{username} password=#{password}"
end
end
end
calling an action with credentials:
curl -I "http://uschi:muschi#hamburg.onruby.dev:5000/"
gives the following logs:
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] Started HEAD "/" for 127.0.0.1 at 2013-10-21 17:40:54 +0200
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] Processing by HomeController#index as */*
[hamburg.onruby.dev] [127.0.0.1] [044cb7ea-56a9-4f] try basic-auth without requesting it: username=uschi password=muschi
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?