Completed 401 Unauthorized in 119ms - ruby-on-rails

I am using devise_saml_authenticatable gem in my rails application to integrate it with external SSO, I have configured my application but I am getting Completed 401 Unauthorized in 119ms from devise/saml_sessions controller.
My config/initializers/devise.rb
config.saml_create_user = true
config.saml_update_user = true
config.saml_default_user_key = :email
config.saml_session_index_key = :session_index
config.saml_use_subject = true
config.idp_settings_adapter = CidpSettingsAdapter
IDP Settings Adapter
class CidpSettingsAdapter
def self.settings(idp_entity_id)
{
issuer: 'https://devidentity.greenfence.com/users/saml/metadata',
assertion_consumer_service_url: 'https://devidentity.greenfence.com/saml/consume',
assertion_consumer_service_binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
#assertion_consumer_logout_service_url: 'https://devidentity.greenfence.com/users/saml/sign_out',
idp_entity_id: 'https://cargill.identitynow.com',
authn_context: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
idp_sso_target_url: 'https://prd02-useast1-sso.identitynow.com/sso/SSOPOST/metaAlias/cargill/idp',
idp_slo_target_url: 'https://prd02-useast1-sso.identitynow.com/sso/IDPSloPOST/metaAlias/cargill/idp',
security: {
authn_requests_signed: false,
logout_requests_signed: false,
logout_responses_signed: false,
metadata_signed: false,
digest_method: XMLSecurity::Document::SHA1,
signature_method: XMLSecurity::Document::RSA_SHA1
},
idp_cert: <<-CERT.chomp
-----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIEIZbEtDANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEOMAwGA1U
CBMFVGV4YXMxDzANBgNVBAcTBkF1c3RpbjESMBAGA1UEChMJU2FpbFBvaW50MR4wHAYDVQQDExVw
cmQwMi11c2Vhc3QxLWNhcmdpbGwwHhcNMTYwMTE5MDM0OTQwWhcNMjYwMTE2MDM0OTQwWjBiMQsw
CQYDVQQGEwJVUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBkF1c3RpbjESMBAGA1UEChMJU2Fp
bFBvaW50MR4wHAYDVQQDExVwcmQwMi11c2Vhc3QxLWNhcmdpbGwwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCRlr1CRIYLomUqTt9Igdrs9dwSW45lLS7lRDh+7WAgIbqIRxLjDH0fJgMi
T14i2gZD+bKyv43epVi6DG8pWrP2qjf8/U1VTr2hMnLrty5ycB9c8DSSh8YSARRIRjxUKrETp70i
BspeMtA3+ZMEnrrz38WlU5zuctzRSr6Q75Yf96tIk1wO+EqRASiNUy+oe/+/LClvPiJLnwdUEnNY
SXgidUvAGxgM639yD0C4cKs++zimwUBcTOgdvPbSJhpG1/CoQcrrdPt78a1RxC3MJJBVG9015SW1
ZkQ5u5sJjFWPzvqd9POgszzc/cj9SjLnh4Y6BFbxZOqkg5Ghn9b8vaElAgMBAAEwDQYJKoZIhvcN=
-----END CERTIFICATE-----
CERT
}
end
end
My config/routes.rb
devise_scope :user do
get 'users/sign_out', to: 'devise/sessions#destroy'
get 'users/submit_verification_code', to: 'aws_cognito#submit_verification_code'
get 'users/request_verification_code', to: 'aws_cognito#request_verification_code'
scope 'users', controller: 'saml_sessions' do
get :new, path: 'saml/sign_in', as: :new_user_saml_session
post :create, path: 'saml/auth', as: :user_saml_session
get :destroy, path: 'saml/sign_out', as: :destroy_user_saml_session
get :metadata, path: 'saml/metadata', as: :metadata_user_saml_session
match :idp_sign_out, path: 'saml/idp_sign_out', via: [:get, :post]
get :sso_dashboard
end
post '/saml/consume' => 'saml_sessions#create'
end

Issue fixed by providing correct issuer name in CidpSettingsAdapter.

Related

Rails no route matches despite rails route output results

Route fails to match despite matching route found in rails routes
No route matches {:action=>"update", :controller=>"croppable_images", :name=>"cover_photo", :record_type=>"Stager"}, possible unmatched constraints: [:name] excluded from capture: No host specified, no public_key specified, no project_id specified
ActionController::UrlGenerationError (No route matches {:action=>"update", :controller=>"croppable_images", :name=>"cover_photo", :record_type=>"Stager"}, possible unmatched constraints: [:name]):
app/controllers/croppable_images_controller.rb:19:in `edit'
127.0.0.1 - - [04/Feb/2022:16:28:44 CST] "GET /stager/profile/cover_photo HTTP/1.1" 500 143893
http://localhost:3000/stager/profile -> /stager/profile/cover_photo
The route call
#edit_image_url = stagers_stager_croppable_image_path('cover_photo')
<%=
form_for(
#croppable_image,
url: #edit_image_url,
method: :put
) do |f|
%>
routes.rb section:
namespace :stagers, path: 'stager' do
resource(
:stager,
path: 'profile',
only: %i[edit update],
path_names: {
edit: ''
}
) do
%w[
profile_photo
cover_photo
].each do |croppable_image_name|
resources(
:croppable_image,
controller: '/croppable_images',
path: '',
param: :name,
only: %i[edit update],
path_names: {
edit: ''
},
defaults: {
record_type: 'Stager'
},
constraints: {
name: croppable_image_name
}
)
end
end
end
The route:
Helper
HTTP Verb
Path
Controller#Action
edit_stagers_stager_croppable_image_path
GET
/stager/profile/:name(.:format)
croppable_images#edit {:record_type=>"Stager", :name=>"profile_photo"}
stagers_stager_croppable_image_path
PATCH
/stager/profile/:name(.:format)
croppable_images#update {:record_type=>"Stager", :name=>"profile_photo"}
PUT
/stager/profile/:name(.:format)
croppable_images#update {:record_type=>"Stager", :name=>"profile_photo"}
GET
/stager/profile/:name(.:format)
croppable_images#edit {:record_type=>"Stager", :name=>"cover_photo"}
PATCH
/stager/profile/:name(.:format)
croppable_images#update {:record_type=>"Stager", :name=>"cover_photo"}
PUT
/stager/profile/:name(.:format)
croppable_images#update {:record_type=>"Stager", :name=>"cover_photo"}
You are setting up that route with a constraint
%w[profile_photo cover_photo].each do |croppable_image_name|
resrources
...
constraints: { name: croppable_image_name }
end
end
So you need to add name: to your route, so this should work:
stagers_stager_croppable_image_path(name: 'profile_photo')
However, since you are not using a specific route name on that each loop, calling it with name set to cover_photo will not work. I think you probably want to use a regex like
constraints: { name: /(cover|profile)_photo/ }
Try declaring your routes like this (sorry I messed up your style)
Rails.application.routes.draw do
namespace :stagers, path: 'stager' do
resource(:stager, path: 'profile', only: %i[edit update], path_names: { edit: '' }) do
resources(
:croppable_image,
controller: '/croppable_images',
path: '',
param: :name,
only: %i[edit update],
path_names: {
edit: ''
},
defaults: {
record_type: 'Stager'
},
constraints: {
name: /(cover|profile)_photo/
}
)
end
end
end

multiple devise auth for WEB and API

Problem Statement
I'm Newbie in Rails and following this tutorial for setting up JWT based authentication in API, and working on an existing web application that uses Devise. My task at the moment is to add a JSON API to the application.
This rails project works fine for a web applications. However, Incase of API I'm getting empty resource while I still have value in params.
Environment
rails (6.1.4)
devise (4.8.0)
devise-jwt (0.9.0)
warden (1.2.9)
warden-jwt_auth (0.6.0)
Controller & Route
app/controllers/api/v1/users/registrations_controller.rb
class Api::V1::Users::RegistrationsController < Devise::RegistrationsController
respond_to :json
skip_before_action :verify_authenticity_token
# POST /resource
def create
super
end
private
def respond_with(resource, _opts = {})
if resource.persisted?
render json: {
status: { code: 200, message: "Signed up sucessfully." },
data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
}
else
render json: {
status: { message: "User couldn't be created successfully. #{resource.errors.full_messages.to_sentence}" }
}, status: :unprocessable_entity
end
end
end
config/routes.rb
# For Web
devise_for :users, controllers: { registrations: "registrations" }
# Authentication
devise_scope :user do
get "/login" => "devise/sessions#new", as: :login
get "/logout" => "sessions#destroy", :as => :logout
get "/signup" => "registrations#new", :as => :signup
scope "my" do
get "profile", to: "registrations#edit"
put "profile/update", to: "registrations#update"
end
end
authenticated :user do
resources :dashboard, only: [:index] do
collection do
get :home
end
end
end
unauthenticated do
as :user do
root to: "devise/sessions#new", as: :unauthenticated_root
end
end
# For API
namespace :api do
namespace :v1 do
devise_for :users, path: '', path_names: {
sign_in: 'login',
sign_out: 'logout',
registration: 'signup'
},
controllers: {
sessions: 'api/v1/users/sessions',
registrations: 'api/v1/users/registrations'
}
end
end
Debugging information
app/controllers/api/v1/users/registrations_controller.rb
| 66: private
| 67: def respond_with(resource, _opts = {})
| 68: byebug
| => 69: if resource.persisted?
| 70: render json: {
| 71: status: { code: 200, message: "Signed up sucessfully." },
| 72: data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
| 73: }
| (byebug) resource
| #<User
id: nil,
email: "",
first_name: "",
last_name: "",
role: "member",
created_at: nil,
updated_at: nil,
jti: nil
>
| (byebug) params
| #<ActionController::Parameters
{
"email"=>"test#test.com",
"first_name"=>"John",
"last_name"=>"Wick",
"password"=>"password",
"controller"=>"api/v1/users/registrations",
"action"=>"create",
"registration"=>{
"email"=>"test#test.com",
"first_name"=>"John",
"last_name"=>"Wick",
"password"=>"password"
}
} permitted: false>
Request
curl -X POST \
http://127.0.0.1:3000/api/v1/signup \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-d '{
"email": "test#test.com",
"first_name": "John",
"last_name": "Wick",
"password": "password"
}'
Response
{
"status": {
"message": "User couldn't be created successfully. Email can't be blank and Password can't be blank"
}
}

How to successfully logout using a Rails 6 API with devise and JWT

I've been stuck for days and searching but I cannot find a correct solution to logout from a devise session using JWT. I had a front made with react and everything works fine on login and searching, but when I logout the page if I don't make a refresh I can't login. I leave the code from devise session controller along side the application controller, route and my middeware build to use redux with my front (I'm working with React to). Thanks in advance and I you need something else, let me know.
Devise::SessionsController
# frozen_string_literal: true
class Api::SessionsController < Devise::SessionsController
respond_to :json, :html
# GET /resource/sign_in
# def new
# super
# end
# POST /resource/sign_in
# def create
# super
# end
# DELETE /resource/sign_out
# def destroy
# super
# end
# protected
private
def revoke_token(token)
# Decode JWT to get jti and exp values.
begin
secret = Rails.application.credentials.jwt_secret
jti = JWT.decode(token, secret, true, algorithm: 'HS256', verify_jti: true)[0]['jti']
exp = JWT.decode(token, secret, true, algorithm: 'HS256')[0]['exp']
user = User.find(JWT.decode(token, secret, true, algorithm: 'HS256')[0]['sub'])
sign_out user
# Add record to blacklist.
time_now = Time.zone.now.to_s.split(" UTC")[0]
sql_blacklist_jwt = "INSERT INTO jwt_blacklist (jti, exp, created_at, updated_at) VALUES ('#{ jti }', '#{ Time.at(exp) }', '#{time_now}', '#{time_now}');"
ActiveRecord::Base.connection.execute(sql_blacklist_jwt)
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
def respond_with(resource, _opts = {})
render json: resource
end
def respond_to_on_destroy
token = request.headers['Authorization'].split("Bearer ")[1]
revoke_token(token)
request.delete_header('Authorization')
render json: :ok
end
end
ApplicationController
class ApplicationController < ActionController::API
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user
protected
def configure_permitted_parameters
added_attrs = %i[username email password password_confirmation remember_me]
devise_parameter_sanitizer.permit(:sign_up, keys: added_attrs)
devise_parameter_sanitizer.permit(:account_update, keys: added_attrs)
end
private
def authenticate_user
if request.headers['Authorization'].present?
token = request.headers['Authorization'].split("Bearer ")[1]
begin
jwt_payload = JWT.decode(token, Rails.application.credentials.jwt_secret).first
#current_user_id = jwt_payload['sub']
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
head :unauthorized
end
end
end
def authenticate_user!(options = {})
head :unauthorized unless signed_in?
end
def current_user
#current_user ||= super || User.find(#current_user_id)
end
def signed_in?
#current_user_id.present?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, skip: %i[registrations sessions passwords]
namespace :api do
devise_scope :user do
post 'signup', to: 'registrations#create'
post 'login', to: 'sessions#create'
delete 'logout', to: 'sessions#destroy'
get 'login', to: 'sessions#create'
end
resources :notes
resources :searches
get 'get_places', to: 'searches#get_places'
end
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
middleware.js
import * as constants from './constants';
import axios from 'axios';
import { logoutUser } from './actions/authActionCreators'
export const apiMiddleware = ({ dispatch, getState }) => next => action => {
if (action.type !== constants.API) return next(action);
dispatch({ type: constants.TOGGLE_LOADER });
const BASE_URL = 'http://localhost:3001';
const AUTH_TOKEN = getState().user.token;
if (AUTH_TOKEN)
axios.defaults.headers.common['Authorization'] = `Bearer ${AUTH_TOKEN}`;
const { url, method, success, data, postProcessSuccess, postProcessError } = action.payload;
console.log('AUTH_TOKEN '+AUTH_TOKEN);
console.log('url '+url);
axios({
method,
url: BASE_URL + url,
data: data ? data : null,
headers: {
'Content-Type': 'application/json', 'Accept': '*/*'
}
}).then((response) => {
dispatch({ type: constants.TOGGLE_LOADER });
if (success) dispatch(success(response));
if (postProcessSuccess) postProcessSuccess(response);
}).catch(error => {
dispatch({ type: constants.TOGGLE_LOADER });
if (typeof(error.response) === "undefined") {
console.warn(error);
postProcessError('An error has ocurred');
} else {
if (error.response && error.response.status === 403)
dispatch(logoutUser());
if (error.response.data.message) {
if (postProcessError) postProcessError(error.reponse.data.message);
}
}
})
};

RubySpec to test a redirect in Ruby controller

I'm trying to code a spec for a method who redirect to a specific path when the user access to provider/plans and provider/prospects
I was trying with many path combinations in the before group, i was trying with this.
before { get :provider_plans_index_path }
before { get :provider_index_plans_path}
before { get :provider_plans_path}
before { get :provider_index_path}
before { get :provider_plans_path}
before { get :provider_planes_path}
base_controller.rb
class Provider::BaseController < ActionController::Base
layout 'provider'
before_action :allowed_pages
def allowed_pages
redirect_to financial_dashboard_path if !requested_action?(params[:controller])
end
def requested_action?(data)
regexp = %r{
^(provider/plans)|
(provider/prospects)$
}x
data.match?(regexp)
end
end
base_controller_spec.rb
require 'rails_helper'
describe Provider::BaseController, type: :controller do
let(:provider) { create(:provider) }
let(:financial) { create(:financial, provider: provider) }
let(:user) { provider.user }
before { login_user user }
describe 'GET plans' do
context 'with not allowed url' do
before { get :provider_planes_path}
it { should redirect_to financial_dashboard_path}
end
end
end
routes.rb
namespace :provider do
get '', to: 'dashboard#index'
get 'dashboard', to: 'dashboard#index'
resources :plans, only: [:index, :create, :update], path: 'planes'
resources :prospects, only: [:index, :show, :create, :update], path: 'prospectos' do
get 'diagnostico', to: 'prospects#show', on: :member, as: :general
patch 'diagnostico', to: 'prospects#update', on: :member
get 'configuracion', to: 'prospects#configuration', on: :member, as: :configuration
end
end
I'm getting this error with all the combinations
ActionController::UrlGenerationError:
No route matches
For me the solution was call the action in spec via another controller
context 'with not allowed url' do
before do
#controller = Provider::PlansController.new
get :index
end
it { should redirect_to financial_dashboard_path }
end

Json Authentication to Devise

I am trying to setup json based authentication on my current rails app. The app's authentication is currently handled by devise.
I have read couple of questions on stackoverflow, but I do not seem to get it working.
routes.rb:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks", :sessions => 'users/sessions' }
SessionsController:
class Users:: SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => :failure)
return sign_in_and_redirect(resource_name, resource)
end
def sign_in_and_redirect(resource_or_scope, resource=nil)
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource ||= resource_or_scope
sign_in(scope, resource) unless warden.user(scope) == resource
return render :json => {:success => true, :redirect => stored_location_for(scope) || after_sign_in_path_for(resource)}
end
def failure
return render:json => {:success => false, :errors => ["Login failed."]}
end
end
Not sure where I have gone wrong. The call I am making is:
JSON:
Content-Type: application/json
Accept: application/json
data: {user: {password: mypass, email: some_email#gmail.com}}
The Error:
MultiJson::DecodeError (756: unexpected token at '{user: {password: mypass, email: some_email#gmail.com}}'):
2012-06-30T18:10:10+00:00 app[web.1]: vendor/bundle/ruby/1.9.1/gems/json-1.6.5/lib/json/common.rb:148:in `parse'
If you paste that JSON into http://jsonlint.com, you get the same error.. Instead, you should wrap your values in quotes:
{
"user": {
"password": "mypass",
"email": "some_email#gmail.com"
}
}

Resources