I'm trying to build an API using rails to use with an Android App, I've configured Devise and Doorkeeper, but I guess I forgot something...
I have this code in my controller:
doorkeeper_for :all
protect_from_forgery with: :null_session
respond_to :json
In the header
Authorization: Bearer b92d41ffd0723a77fcb8acd03e6e5be3b2f3036f07c9619f5bfb62611e31f42d
When I use GET method it works, but POST or DELETE don't
I get this error when I try to do a POST in my controller
{"error": "You need to sign in or sign up before continuing."}
Any suggestions?
Update
doorkeeper.rb
Doorkeeper.configure do
orm :active_record
resource_owner_authenticator do
current_usuario || warden.authenticate!(:scope => :usuario)
end
resource_owner_from_credentials do |routes|
request.params[:usuario] = {:email => request.params[:username], :password => request.params[:password]}
request.env["devise.allow_params_authentication"] = true
request.env["warden"].authenticate!(:scope => :usuario)
end
end
I have made no changes in default values of Devise
Related
So I'm trying to configure Rails with Google oauth via devise, i have followed official docs described here. After everything i get this error when clicking the google signup button
Access to fetch at 'url' (redirected from 'http://localhost:3000/users/auth/google_oauth2') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Googling around i found you need to enable CORS, naturally i did just that
in my application.rb in added this
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :patch, :put]
end
end
This is my callbacks controller
class Users::CallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token
def google_oauth2
admin = User.from_omniauth(from_google_params)
redirect_to root_path
end
private
def from_google_params
#from_google_params ||= {
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image,
is_member: false
}
end
def auth
#auth ||= request.env['omniauth.auth']
end
end
EDIT
So after alot of trial n errors i have it working, kinda. So when i click the signup with google it still throws an error,
But, when clicking the link in the google console it successfully goes there, any ideas ?
Try this setup with devise at master branch and gem omniauth-rails_csrf_protection
devise.rb
config.omniauth :google_oauth2, "API_KEY", "API_SECRET"
routes.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
user.rb
devise :omniauthable, omniauth_providers: [:google_oauth2]
app/controllers/users/omniauth_callbacks_controller.rb:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'Google'
sign_in_and_redirect #user, event: :authentication
else
session['devise.google_data'] = request.env['omniauth.auth'].except('extra') # Removing extra as it can overflow some session stores
redirect_to new_user_registration_url, alert: #user.errors.full_messages.join("\n")
end
end
end
user.rb
def self.from_omniauth(access_token)
data = access_token.info
user = User.where(email: data['email']).first
unless user
user = User.create(
email: data['email'],
password: Devise.friendly_token[0,20])
end
user
end
gemfile:
gem "devise", github: "heartcombo/devise", branch: "master"
gem "omniauth-rails_csrf_protection"
gem "omniauth-google-oauth2"
view:
<%= link_to "Sign in with Google", user_omniauth_authorize_path(:google_oauth2), method: :post %>
I have a Rails App (that is mainly a JSON API)
I'm Using Devise for authentication using JSON request from whatever source (web , mobile)
and I'm using Simple Token Authentication to authenticate users using HTTP headers.
I'm not sure how the implementation should look like, but I have drafted an implementation that almost works.
There is only one problem. and that is when user tries to sign out... typically it should invalidate the authentication token for the user... but it doesn't, I'm not sure where is the problem really... whether it's with the Devise or the Simple Token Auth... so any help is greatly appreciated.
but here is the code
# router
devise_for :users, controllers: { sessions: 'sessions',
registrations: 'registrations' }
api vendor_string: 'app', default_version: 1, path: '', format: 'json' do
version 1 do
cache as: 'v1' do
resource :some_resource
end
end
the session controller is like this
class SessionsController < Devise::SessionsController
respond_to :json
skip_filter :verify_signed_out_user, only: :destroy
def create
self.resource = warden.authenticate!(scope: resource_name)
render :create, status: :created
end
def destroy
current_user.authentication_token = nil
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ take note of this line
current_user.save!
super
end
end
the previous noted line seems to have a problem... when a user provide a wrong token header, it's still working and the current user refers to the user who shouldn't be authenticated in the first place.. for example here are 2 calls fro, the specs
describe 'DELETE sessions#destroy' do
let(:user) { Fabricate(:confirmed_user) }
let(:auth_token) { user.authentication_token }
describe 'with request headers' do
context 'valid credentials' do
it 'Returns 204' do
delete '/users/sign_out', {}, {
HTTP_CONTENT_TYPE: 'application/json',
HTTP_ACCEPT: "application/vnd.app+json; version=1",
"X-User-Email" => user.email,
"X-User-Token" => user.authentication_token
}
user.reload
expect(response.status).to eq 204
expect(user.authentication_token).not_to eq auth_token
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is ok cause it's the valid user
end
end
context 'invalid credentials' do
it 'Returns 204' do
delete '/users/sign_out', {}, {
HTTP_CONTENT_TYPE: 'application/json',
HTTP_ACCEPT: "application/vnd.app+json; version=1",
"X-User-Email" => user.email,
"X-User-Token" => 'Invalid'
}
user.reload
expect(response.status).to eq 204
expect(user.authentication_token).to eq auth_token
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this FAILS
# why did the user get new auth token when didn't sign out ????
end
end
end
this is also reported on Github
and for completeness here is the application controller
class ApplicationController < ActionController::Base
# The API responds only to JSON
respond_to :json
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# default to protect_from_forgery with: :exception
protect_from_forgery with: :null_session
# Token Authenticatable for API
acts_as_token_authentication_handler_for User
end
From simple_authentication_token page on github, the current_user.authentication_token will automatically generated if it was blank (nil) on each time current_user will be saved.
Assuming user is an instance of User, which is token authenticatable: each time user will be saved, and user.authentication_token.blank? it receives a new and unique authentication token (via Devise.friendly_token).
Update
Add acts_as_token_authentication_handler_for User in your sessions_controller.rb
Please read on https://github.com/gonzalo-bulnes/simple_token_authentication/issues/224 . I think that is normal behaviour. You need delete the token on the client side(device)
I am trying to authenticate my new Shopify app. First, my authenticate method redirects the shop owner to Shopify's authentication page:
def authenticate
ShopifyAPI::Session.setup({:api_key => "123", :secret => "456"})
session = ShopifyAPI::Session.new("someshop.myshopify.com")
redirect_to session.create_permission_url(["read_orders"], "https://myapp.com/shopify/post_authenticate?user=someshop")
end
Once the shop owner has approved the integration, the redirect uri triggers my post_authenticate method:
def post_authenticate
ShopifyAPI::Session.setup({:api_key => "123", :secret => "456"})
session = ShopifyAPI::Session.new("#{params[:user]}.myshopify.com")
token = session.request_token(:code => params[:code], :signature => params[:signature], :timestamp => params[:timestamp])
end
But the request_token method returns the following error:
#<ShopifyAPI::ValidationException: Invalid Signature: Possible malicious login>
I have read somewhere that you need to be in the same ShopifyAPI session while doing all of this, but it does not say so in the documentation. And the example app takes a very different approach than the documentation.
As per my comment, I utilize the omniauth methodology for authenticating. Here's a gist of the code for reference. https://gist.github.com/agmcleod/7106363317ebd082d3df. Put all the snippets below.
class ApplicationController < ActionController::Base
protect_from_forgery
force_ssl
helper_method :current_shop, :shopify_session
protected
def current_shop
#current_shop ||= Shop.find(session[:shop_id]) if session[:shop_id].present?
end
def shopify_session
if current_shop.nil?
redirect_to new_login_url
else
begin
session = ShopifyAPI::Session.new(current_shop.url, current_shop.token)
ShopifyAPI::Base.activate_session(session)
yield
ensure
ShopifyAPI::Base.clear_session
end
end
end
end
In my login controller:
def create
omniauth = request.env['omniauth.auth']
if omniauth && omniauth[:provider] && omniauth[:provider] == "shopify"
shop = Shop.find_or_create_by_url(params[:shop].gsub(/https?\:\/\//, ""))
shop.update_attribute(:token, omniauth['credentials'].token)
shopify_session = ShopifyAPI::Session.new(shop.url, shop.token)
session[:shop_id] = shop.to_param
redirect_to root_url
else
flash[:error] = "Something went wrong"
redirect_to root_url
end
end
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :shopify, Settings.api_key, Settings.api_secret,
scope: 'write_products,write_script_tags,read_orders',
setup: lambda { |env| params = Rack::Utils.parse_query(env['QUERY_STRING'])
env['omniauth.strategy'].options[:client_options][:site] = "http://#{params['shop']}" }
end
Then in your routes file, map the create action of your session appropriately:
match '/auth/shopify/callback', :to => 'login#create'
From there i use the shopify_session method as an around filter on the appropriate controllers.
I've been struggling trying to get my API working with CORS. I followed the instructions from:
http://kellishaver.tumblr.com/post/40758797489/cors-headers-with-devise
Added this to my application.rb:
config.middleware.use "Cors"
config.middleware.insert_before Warden::Manager, "Cors"
Added this to /lib/cors.rb:
class Cors
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'DELETE, GET, HEAD, OPTIONS, POST, PUT'
headers['Access-Control-Max-Age'] = "1728001"
[status, headers, response]
end
end
As I'm trying to solve the same problem and allow my API to be called from Chrome (or anywhere else). Problem is that all my calls work except for my custom API Devise calls. My Devise API calls get a weird 404 error for "OPTIONS" when I look at it in Chrome dev tools.
My Devise API looks like this...
class Api::V1::SessionsController < Devise::SessionsController
skip_before_filter :verify_authenticity_token,
:if => Proc.new { |c| c.request.format == 'application/json' }
respond_to :json
def create
warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
render :status => 200,
:json => { :success => true,
:info => "Logged in",
:data => { :auth_token => current_user.authentication_token },
:id => current_user.id}
end
...
Any ideas on why my custom Devise API calls aren't working but my others are?
Thanks!
Got it working. Just switched to the rack-cors gem instead of the code above and followed the blog example here:
https://gist.github.com/AdrianTeh/3561310
How would I write a method to be used in rspec testing to access pages that require a username and password for HTTP Digest Authentication. For example, this test...
it "edit" do
http_login
post :edit, id: #post
assigns[:post].should eq(#post)
end
needs http_login method to be something like this...
def http_login
user = {"username" =>
Digest::MD5.hexdigest(["username","Application","password"].join(":"))}
request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Digest.encode_credentials(?,?,?,?)
end
My question is what do I put in the four arguments for the encode credentials. The arguments are going to be http_method, credentials, password, password_is_ha1 but I'm unsure how to write http_method and credentials to implement in the tests.
Solution here: https://gist.github.com/1282275
recopied here for posterity
# Adds support for http digest authentication in Rails 3
# Inspired by: http://lightyearsoftware.com/2009/04/testing-http-digest-authentication-in-rails/
# Place this code in test/test_helper.rb
# In your test, call authenticate_with_http_digest prior to calling get, post, put or delete
# Tested with Rails 3.0.7
class ActionController::TestCase
require 'digest/md5'
def authenticate_with_http_digest(user = API_USERNAME, password = API_PASSWORD, realm = API_REALM)
ActionController::Base.class_eval { include ActionController::Testing }
#controller.instance_eval %Q(
alias real_process_with_new_base_test process_with_new_base_test
def process_with_new_base_test(request, response)
credentials = {
:uri => request.url,
:realm => "#{realm}",
:username => "#{user}",
:nonce => ActionController::HttpAuthentication::Digest.nonce(request.env['action_dispatch.secret_token']),
:opaque => ActionController::HttpAuthentication::Digest.opaque(request.env['action_dispatch.secret_token'])
}
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Digest.encode_credentials(request.request_method, credentials, "#{password}", false)
real_process_with_new_base_test(request, response)
end
)
end
end
Here's a solution for RSpec 3.1 and Rails 4 HTTP Digest Auth testing: https://gist.github.com/murbanski/6b971a3edc91b562acaf