How to create a secure connection in form fields with Sinatra - ruby

I used a front-end and back-end API server to implement a Ruby/Sinatra web application. The form will post to the '/login' route in the front-end app.rb:
post '/login' do
uri = URI.join("http://#{settings.api}:#{settings.api_port}",
"/user/", "validate")
response = Net::HTTP.post_form(
uri, 'email' => params[:email],
'password' => params[:password])
h = response.code == "200" || response.code == "401" ?
JSON.parse(response.body) : {}
if h["status"] == "success"
# Save the user id inside the browser cookie.
# This is how we keep the user
# logged in when they navigate around our website.
session[:user_id] = h["user_id"]
puts session[:user_id]
redirect '/home'
else
# If user's login doesn't work, send them back to the login form.
flash[:notice] = "Login failed due to #{h["status"]}"
redirect '/login'
end
end
The post request and its parameters are sent through HTTP to the back-end server. And the back-end API server will respond with JSON which has a status field to suggest if the user is authenticated successfully.
I have the following message in the browser that says "connection not secure".
enter image description here. Is there extra safety configuration that I have to include in the front-end app.rb?

You can try adding the following configrations in your app.rb file:
configure :development, :test do
set :force_ssl, true
end
configure :production do
set :force_ssl, true
end
See "Forcing SSL in a Sinatra App" about forcing SSL in Sinatra applications and how it could be helpful to your situation.

Related

Routing error: No route matches [PATCH] "/users/sign_in" for devise update password

I'm trying to verify my update_password api which updates current user password. I'm using devise and my application is SPA, angular on the front end side. I'm currently getting this error when I try to verify the api with postman client.
def update
#user = User.find(current_user.id)
if #user.update_with_password(user_params)
# Sign in the user by passing validation in case their password changed
sign_in #user, :bypass => true
redirect_to root_path , notice: "Password was successfully changed"
else
# render "edit"
respond_to do |format|
format.html { redirect_to edit_user_registration_path, alert: #user.errors.full_messages.first.to_s }
end
# render "edit"
end
end
It looks to me like you need to specify the user in your headers in Postman. The uri you listed in Postman is to update a user, but your error is saying there's no user sign in path, which I would assume means it doesn't have access to a user to update.

Devise Json Authentication

I am writing backend of an app in Rails. As I work on the backend, I need to give the frontend developer a REST API to start building the frontend. Eventually, the frontend and backend will reside together in a single app, but for now they are separate.
For time being I have enabled Cross-origin resource sharing in my app, by adding following to ApplicationController:
config.action_dispatch.default_headers.merge!({
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => '*'
});
For now, I have also turned off CSRF tokens by adding following to application.rb:
skip_before_filter :verify_authenticity_token
I am using Devise for authenticating users. To make Devise work with JSON requests, I have done following:
In devise.rb
config.navigational_formats = ['*/*', :html, :json]
In routes.rb
devise_for :users, :controllers => {:omniauth_callbacks => "omniauth_callbacks", :sessions => 'sessions', :registrations => 'registrations' }
My SessionsController
class SessionsController < Devise::SessionsController
#todo had to do following to support logging in through ajax. need to add logic to send back error response when login fails.
#todo see http://stackoverflow.com/questions/5973327/using-devise-1-3-to-authenticate-json-login-requests/8402035#8402035 and
#todo https://web.archive.org/web/20130928040249/http://jessehowarth.com/devise
#todo see http://stackoverflow.com/questions/11277300/devise-failure-authentication-via-json-sends-back-html-instead-of-json
def create
respond_to do |format|
format.html { super }
format.json {
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
sign_in(resource_name, resource)
return render :json => {:success => true, :user => resource}
}
end
end
def destroy
respond_to do |format|
format.html { super }
format.json {
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
render :json => {}
}
end
end
def failure
render :json => {:success => false, :errors => ["Login Failed"]}, :status => 422
end
end
I have a extended Devise's RegistrationsController as well as indicated in routes.rb, but am not posting its content here, as I don't think it is relevant to this question.
With the above setup I am able to send an ajax request to '/users/sign_in' with user[email] and user[password] parameters and have the user signed in. The response looks something like this:
{
success: true
user: {
authentication_token: "SNa2kPqkm5ENsZMx7yEi"
created_at: "2014-12-16T02:40:39.179Z"
email: "xyz#xyz.com"
id: 99999
name: null
provider: null
uid: null
updated_at: "2014-12-17T02:29:31.537Z"
}
}
Now how do I use the authentication_token I received in the sign_in response to send requests to other controller actions that require user to be authenticated? Do I need to set this token in a request header? I am not able to find information on how to use this token. Please help.
It seems following as described in the gist here, the answer is that you send the suer's email and authetication_token with every request to the backend. You may choose to send it in request header or simply as parameters. You simply modify the method that checks the email and token and signs in the user in ApplicationController accordingly. This is my ApplicationController (I am now sending the email and token as parameters in the request):
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
#todo remove this once ui is integrated. following turns off the csrf token:
skip_before_filter :verify_authenticity_token
#todo begin code to support authentication using token
# This is our new function that comes before Devise's one
before_filter :authenticate_user_from_token!
# This is Devise's authentication
before_filter :authenticate_user!
private
def authenticate_user_from_token!
user_email = params[:user_email].presence
user = user_email && User.find_by_email(user_email)
# Notice how we use Devise.secure_compare to compare the token
# in the database with the token given in the params, mitigating
# timing attacks.
if user && Devise.secure_compare(user.authentication_token, params[:user_token])
sign_in user, store: false
end
end
#todo end code to support authentication using token
end
I forgot to mention in my post that I had already added the migration to add a authentication_token column to User model. Also, I had to add following in the User model (as described in the gist), so that an authentication token is generated each time a user is created/updated:
#todo begin code to support ajax authentication of users
#todo see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# You likely have this before callback set up for the token.
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.where(authentication_token: token).first
end
end
#todo end code to support ajax authentication of users

google drive ruby api login with oauth2, what is "authorization code"

I try to use login_with_oauth2 with google_drive, but I can't understand what is authorization code in #google_drive.rb doc
client = OAuth2::Client.new(
"522807807986-gjotv2np4tdqp4do8sq0gds0p2bqugtf.apps.googleusercontent.com",
'fmWlfzejvx_UtS3CKq2Sl-WQ',
:site => "https://accounts.google.com",
:token_url => "/o/oauth2/token",
:authorize_url => "/o/oauth2/auth"
)
auth_url = client.auth_code.authorize_url(
:redirect_uri => "urn:ietf:wg:oauth:2.0:oob
http://localhost"
)
# Redirect the user to auth_url and get authorization code from redirect URL.
authorization_code = ''
auth_token = client.auth_code.get_token(
authorization_code, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob
http://localhost")
session = GoogleDrive.login_with_oauth(auth_token.token, 'http://localhost:8087')
OAuth2 is the two-steps authorization mechanism. AFAIU, you have your code simply copy-pasted from official documentation, but unfortunately that’s not a working code. It’s just an example. Take a look at the comment marked red there: it’s very important:
# Redirect the user to auth_url and get authorization code from redirect URL.
You are to specify the correct pingback URI for Google server to send the response with your authorization_code (now there states localhost, which is problematically for Google to understand your IP to send you feedback.)
The summing up: you provide Google with credentials, it replies with JSON, containing your authorization_code to the address you specified. After this phase is successfully completed, you may proceed requesting auth_token.

Why do I get a duplicated curb request?

I'm creating an application, which has authentication based on external API with login/register methods. I have a simple controller called RegistrationsController which fires a request using Curb.
This is the controller:
class RegistrationsController < ApplicationController
def new
end
def create
if params[:user][:email].present? && params[:user][:password].present? && params[:user][:phone].present? && params[:user][:login].present?
# API request
password = params[:user][:password]
body = {
"register" => {
"password" => password,
"email" => params[:user][:email],
"phone" => params[:user][:phone],
"login" => params[:user][:login]
}
}
c = Curl::Easy.http_post("http://domain.com/register", body.to_json
) do |curl|
curl.headers['Content-Type'] = 'application/json'
curl.headers['application'] = 'appname'
curl.headers['device'] = 'www'
end
c.perform
response_body = JSON.parse(c.body_str)
throw response_body # This line ALLWAYS gives me 'login taken' error
return
else
#user = User.new(params[:user])
render action: "new", notice: 'Error'
end
end
end
(I also have a views/registrations/new.html.slim view with a simple form but it's not important right now.)
My routes look like this:
match 'users/sign_up' => 'registrations#new', :via => :get, :as => :user_register
match 'users/sign_up' => 'registrations#create', :via => :post, :as => :user_create
My application, after I click the "Register" button on the registrations#new page, is triggering the Curb request two times. As a result, I'm always getting a 'login taken' error. The user is registered successfully but I'm not getting any result from the first request, just from the second one.
It's somehow caused by Rails and I'm 100% sure about it because it can be seen in the API server logs that the request is triggered twice. Also, I have exactly the same script written in PHP and, in there, the registration works fine.
In my Rails dev console, the request is triggered just one time so it's really strange.
Does anyone have any idea what is going on here?
I found the answer.
If anyone struggles with something similar, it was caused by the c.perform line. Just remove it and it will work fine.
I should study the docs better in the future.

"Error validating client secret." 404 with Facebook Oauth and ruby

I am trying to implement facebook authentication for an app with warden, after the user allows facebook auth and redirects to my app callback with the token I get a 400 while consuming the api. My warden strategy is this:
class Facebook < Warden::Strategies::Base
def client
#client ||= OAuth2::Client.new MyApp::Facebook::AppID, MyApp::Facebook::AppSecret, :site => 'https://graph.facebook.com'
end
def params
#params ||= Rack::Utils.parse_query(request.query_string)
end
def authorize_url
client.web_server.authorize_url :redirect_uri => request.url, :scope => 'email,publish_stream'
end
def authenticate!
throw(:halt, [302, {'Location' => authorize_url}, []]) unless params['code']
facebook = client.web_server.get_access_token params['code'], :redirect_uri => request.url
rescue OAuth2::HTTPError => e
puts e.response.body
end
end
Strategies.add :facebook, Facebook
The result of printing the response body is this:
{"error":{"type":"OAuthException","message":"Error validating client secret."}}
I am pretty shure the app id and app secret are the ones provided by FB.
Thanks.
I've seen that error message many times. Here are the things I would double check:
your domain is the same as what you listed in the facebook callback url
the app id is correct (actually print this out on a page, sometimes y
the app secret is correct
Add redirect_uri while creating the object of facebook that will fix the issue.
Redirect the user to https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL
After user click allow, it'll hit our Redirect Uri
At that point we'll get the code and we need to do a server side HTTP Get to the following Url to exchange the code with our oAuth access token:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&
client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
Now at step 3, I kept on getting Http 400 response back.
So after some research, I found out that on that redirect_uri that we submitted on step 3 doesn't do anything but validate the request. Thus, the value need to match with step 2.
I also get the same error and i resolved by doing as below:
double check your client_id, client_secret, redirect_uri.
Add Accept: "application/json" header to thye request
fetch(
`https://graph.facebook.com/v15.0/oauth/access_token?client_id=${process.env.FACEBOOK_APP_ID}&redirect_uri=${process.env.FACEBOOK_REDIRECT_URI}&client_secret=${process.env.FACEBOOK_APP_SECRET}&code=${code}`,
{
method: "GET",
headers: {
Accept: "application/json",
},
}
)

Resources