My aplication initially used Devise for authentication, but I recently had to install the omniauth-keycloak gem to support authentication through Keycloak. And it worked as expected in Development and Validation, relying on the same Keycloak instance.
But since the last deployment in Validation, it looks like OmniAuth does not query Keycloak anymore, and switches directly to the failure method of the OmniauthCallbacksController. It returns this trace when the user clicks on the login link to request for the Keycloak login form:
Started POST "/users/auth/keycloakopenid" for 10.228.50.117 at 2022-06-30 15:41:29 +0200
Processing by Users::OmniauthCallbacksController#failure as HTML
Parameters: {"authenticity_token"=>"M+Zr3ZcZc08mthj3LBuoS4Jwb9GBdBsm8m9nmqGDNyssgIf8rmtyJQbpOcmK4OTidRNK3mzZpOOnaTSfJFJlEQ=="}
Redirected to https://l821000109918b.adr.admin.ch/users/sign_in
Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
Started GET "/users/sign_in" for 10.228.50.117 at 2022-06-30 15:41:29 +0200
Netwotk analysis does not even report a request to the Keycloak server.
Here is the Devise configuration:
config/initializers/devise.rb
config.omniauth :keycloak_openid,
"BFS.SIS",
client_options: { base_url: '', site: Rails.application.credentials.integration[:authentication_url], realm: "bfs-sis-a" },
strategy_class: OmniAuth::Strategies::KeycloakOpenId
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def keycloakopenid
# Read OmniAuth token
auth = request.env["omniauth.auth"]
# auth.extra.raw_info.resource_access.map {|h| h[1].roles.map(&:itself).join(',')} provides the list of all roles
# resources_accesses.first[1].roles provides an array hash
#user = User.from_omniauth(auth) # Finds or create the user
if #user.persisted?
# The user has been identified or created: then manage groups, activities access and roles
# 1-set roles in user's external_roles array
# 2-set statistical activities in user's preferred_activities array
# 3-make sure that the user is a member of the relevant groups (Everyone and External users)
--- do some stuff ---
sign_in_and_redirect #user, event: :authentication
else
redirect_to new_user_session_path
end
end
def failure
redirect_to new_user_session_path
end
end
The link requesting Keycloak from the devise/sessions/new.html.erb view is given by:
<%= link_to "Sign in with SIS Portal", user_keycloakopenid_omniauth_authorize_path,
method: :post,
class: "mat-flat-button mat-button-base mat-primary",
style: "width:200px;" %>
Since it keeps working correctly in Development, and I did not touch this feature for several months, I am wandering what may have happened, and how to investigate deeper ine the code execution?
Thank you for your suggestions!
Related
I am unable to register a user via LinkedIn on my production site. Its showing an error like this :
Completed 500 Internal Server Error in 202ms (ActiveRecord: 3.8ms)
[f842309d-189a-4edc-bafb-a6a567ea8fcd]
[f842309d-189a-4edc-bafb-a6a567ea8fcd] BCrypt::Errors::InvalidHash (invalid hash):
[f842309d-189a-4edc-bafb-a6a567ea8fcd]
[f842309d-189a-4edc-bafb-a6a567ea8fcd] app/models/user.rb:82:in `from_omniauth'
[f842309d-189a-4edc-bafb-a6a567ea8fcd] app/controllers/users/omniauth_callbacks_controller.rb:9:in `linkedin'
It's working fine in my local machine.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def linkedin
#user = User.from_omniauth(auth_hash, current_user, session)
remember_me(#user)
session[:user_id] = #user.id
if !(#user.img_url == auth_hash.info.picture_url)
#user.update_attributes(:img_url => auth_hash.info.picture_url)
end
if not #user.persisted?
session["devise.linkedin_data"] = auth_hash
render body: session.to_json
end
sign_in_and_redirect(:user, #user)
end
end
User Model
def self.from_omniauth(auth, signed_in=nil, session)
user.password = Devise.friendly_token unless user.encrypted_password.present?
end
Can someone help me understand this error and how I may be able to fix it, please?
Have you tried upgrading your bcrypt to 3.1.12?
Here is the link to GITHUB ISSUE
Have you set all devise config secrets correctly? Check your devise initializer and your ENV variables on your production server. I think the server is missing a ENV variablee to tell bcrypt what salt to use to store the password
I have a login page on which the authentication can be successful or not. Here is the page new.html.erb:
<%=form_with scope: :session, url: sessions_path, local: true, html: {class: "login-form"} do |f| %>
<%= f.label :email, t("session.new.email") %>
<%= f.email_field :email %>
<%= f.label :password, t("session.new.password") %>
<%= f.password_field :password %>
<%= f.submit t('session.new.login'), class: "submit" %>
<% end %>
It is associated to a sessions_controller.rb, which is the following:
class SessionsController < ApplicationController
def create
# Find the user with the matching email
user = User.find_by(email: params[:session][:email].downcase)
# Check the user exists in DB and that the provided password matches
if user && user.authenticate(params[:session][:password])
# Log the user through the session helper
log_in user
# Redirect to the hive
redirect_to ideas_path
else
# The authentication failed. Display an error message
flash.now[:error] = I18n.t('session.new.invalid_credentials')
# The render is done to reinitiate the page
render :new
end
end
end
In my routes.rb, I just have for this purpose:
resources :sessions
When executing rails routes, I have the following declared routes:
Now my problem is on the login fail. In my controller, in this case, I add a message in the flash messages and then re-render the same page new.html.erb. But in the browser, the login request POST has been sent on the url /sessions. The problem is the current URL on my browser becomes /sessions instead of staying on /sessions/new. This is as if the POST request changed the URL in my browser. But this is in fact just an AJAX request, isn't it?
I have found this blog post that wonders the same about this phenomenon (I'm not the author)
I have found a workaround, but I'd prefer avoid using it and understand the bevahior. If i replace my routes by the following, this works:
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
I can understand why this works: the get and post url are the same, so the browser doesn't change its URL.
Have you any idea?
EDIT:
I finally found a solution. I'm not sure this is the "rails way", but this works as expected. I have just changed the controller to do a redirection to the same page, with a flash request to transmit the login fail information:
def create
# Find the user with the matching email
user = User.find_by(email: params[:session][:email].downcase)
# Check the user exists in DB and that the provided password matches
if user && user.authenticate(params[:session][:password])
# Log the user through the session helper
log_in user
# Redirect to the hive
redirect_to ideas_path
else
# The authentication failed. Display an error message through a flash
# message after redirect to the same page
redirect_to new_session_path, alert: I18n.t('session.new.invalid_credentials')
end
end
When the form gets submitted the browser performs a regular HTTP POST requsest to the /sessions endpoint. No AJAX there.
The way your routes are configured this POST request will be handled by your sessions#create action.
Notice the code there. You'll see that the happy path (successful login) calls redirect_to. In case of login errors though, the controller calls render.
The difference is that in the first case the response is a 302 Redirect which the browser follows. That's why you see the URL changing in the browser. In the second case the response is just 200 OK with a bunch of HTML for the browser to render. The URL won't change because the browser wasn't instructed to navigate elsewhere.
Here's an extensive explanation of how redirects work in the browser in case you're interested.
I've had an instagram omniauth integration working now for a few years and I haven't changed anything in a long time but my customers are occasionally out of the blue getting an error when connecting their Instagram accounts:
Started GET "/auth/instagram/callback?code=d3b1c4d88e2f440b8a8a98037b821c15&state=2565d32ecd3cc5967f32d8c945db6ffba74dc784100777f2" for 127.0.0.1 at 2016-12-15 15:29:59 -
0800
I, [2016-12-15T15:29:59.276712 #32520] INFO -- omniauth: (instagram) Callback phase initiated.
E, [2016-12-15T15:29:59.519801 #32520] ERROR -- omniauth: (instagram) Authentication failure! invalid_credentials: OAuth2::Error, :
{"code": 400, "error_type": "OAuthException", "error_message": "Matching code was not found or was already used."}
The only way I can consistently get this to happen is to connect the account from my app, then remove permissions from the instagram end, then try to reconnect again on my app, but it seems like some of my customers are getting this the first time they connect their instagram accounts.
Anyone encountered this before? My config/initializers/omniauth.rb looks like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :instagram, ENV['INSTAGRAM_CLIENT_ID'], ENV['INSTAGRAM_CLIENT_SECRET'], scope: 'basic comments public_content'
end
Edit:
I'm using omniauth 1.3.1 and omniauth-instagram 1.0.2
My routes is just your basic omniauth route:
get '/auth/:provider/callback', to: 'omniauth_callbacks#create'
And my controller action is a bit complex but it looks like this:
class OmniauthCallbacksController < ApplicationController
def create
if user_signed_in?
social_account = SocialAccount.from_omniauth(auth_params, current_user)
social_account.save!
redirect_to root_path
else
user = User.from_omniauth auth_params
sign_in_and_redirect user
end
end
private
def auth
request.env['omniauth.auth']
end
def auth_params
{
provider: auth['provider'],
uid: auth['uid'],
token: auth['credentials']['token'],
secret: auth['credentials']['secret'] || auth['credentials']['refresh_token'],
name: auth['info']['nickname'] || auth['info']['name'],
emails_sent: 0
}
end
end
Basically it creates a new user and signs them in if they haven't connected, and it updates their login info if they have.
There seems to be a problem on Instagram around getting access tokens. https://news.ycombinator.com/item?id=13178789
I am working on a project that is divided in two apps :
- one rails JSON API that is dealing with the database and is rendering data as JSON
- one "front-end" rails app that is sending requests to the API whenever it needs and displaying the json data in a nice way.
Authentification for the API is token based using gem'simple_token_authentication' meaning that for most of the requests that are sent to the API you have to send the user token & his email in the header for the request to be authorized.
The one who worked on the project before me had also installed Devise authentification system on the API side to allow direct access to the API methods from the navigator after successfull login with email & password.
I just started coding on the "front-end app" that is supposed to request the API and I am having trouble especially with the authentification system.
As Devise was already installed on the API, I thought it would be a good idea to make the user login on the front-end app which would then request devise's methods present on the API for creating user, auth, reseting password...
The problem is that devise's methods are rendering html and not JSON so I actually had to override most of devise's controller. To give you a quick idea of how it works :
You fill the sign up form on the front-end app then the params are sent to the front-end app controller that is then requesting devise's register user method on the API :
1) front-end app controller :
def create
# Post on API to create USER
#response = HTTParty.post(ENV['API_ADDRESS']+'users',
:body => { :password => params[:user][:password],
:password_confirmation => params[:user][:password_confirmation],
:email => params[:user][:email]
}.to_json,
:headers => { 'Content-Type' => 'application/json' })
# si le User est bien crée je récupère son email et son token, je les store en session et je redirige vers Account#new
if user_id = #response["id"]
session[:user_email] = #response["email"]
session[:user_token] = #response["authentication_token"]
redirect_to new_account_path
else
puts #response
#errors = #response["errors"]
puts #errors
render :new
end
end
2) API overrided devise controller :
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
#user = User.new(user_params)
if #user.save
render :json => #user
else
render_error
end
end
def update
super
end
private
def user_params
params.require(:registration).permit(:password, :email)
end
def render_error
render json: { errors: #user.errors.full_messages }, status: :unprocessable_entity
end
end
This works ok. Here I send back the user that was just created on the API as JSON and I store is auth token and his email in the session hash.
My problem is with the reset_password method for which I am trying to reuse some of devise code.
First, I ask for a reset of the password which generates a reset password token for the user who requested the change. This also generates an email to the user with a link (with the token inside) pointing to the reset password form for the specific user. This is working well. I am getting the link in the email then going to the edit_password form on my front-end app :
Change your password
<form action="/users/password" method='post'>
<input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden">
<%= hidden_field_tag "[user][reset_password_token]", params[:reset_password_token] %>
<%=label_tag "Password" %>
<input type="text" name="[user][password">
<%=label_tag "Password Confirmation" %>
<input type="text" name="[user][password_confirmation]">
<input type="Submit" value="change my password">
</form>
When the form is submitted it goes through my front-end app controller :
def update_password
#response = HTTParty.patch(ENV['API_ADDRESS']+'users/password',
:body => {
:user => {
:password => params[:user][:password],
:password_confirmation => params[:user][:password_confirmation],
:reset_password_token => params[:user][:reset_password_token]
}
}.to_json,
:headers => { 'Content-Type' => 'application/json' })
end
which then calls my overrided Devise::PasswordController (update method) :
# app/controllers/registrations_controller.rb
class PasswordsController < Devise::RegistrationsController
# POST /resource/password
def create
if resource_params[:email].blank?
render_error_empty_field and return
end
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
render_success
else
render_error
end
end
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
render_success
else
render_error
end
end
private
# TODO change just one big method render_error with different cases
def render_success
render json: { success: "You will receive an email with instructions on how to reset your password in a few minutes." }
end
def render_error
render json: { error: "Ce compte n'existe pas." }
end
def render_error_empty_field
render json: { error: "Merci d'entrer un email" }
end
end
However the request is always Unauthorized :
Started PATCH "/users/password" for ::1 at 2016-02-05 11:28:30 +0100
Processing by PasswordsController#update as HTML
Parameters: {"user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "reset_password_token"=>"[FILTERED]"}, "password"=>{"user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "reset_password_token"=>"[FILTERED]"}}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
I dont understand why is this last request unauthorized ?
Your predecessor likely made a mess of things on the API side just for his convenience.
We know that using cookies for API's is a really bad idea since it leaves the doors wide open for CSRF/XSRF attacks.
We can't use the Rails CSRF protection for an API because it only works as sort of guarantee that the request originated from our own server. And an API that can only be used from your own server is not very useful.
Devise by default uses a cookie based auth strategy because thats what works for web based applications and Devise is all about making auth in web based applications easy.
So what you should do is either remove Devise completely from the API app or convert Devise to use a token based strategy. You also should consider removing the sessions middleware from the API app. Also the Devise controllers are so heavily slanted towards client interaction so that trying to beat them into API controllers is going to be very messy.
Updating a password in an API is just:
class API::V1::Users::PasswordsController
before_action :authenticate_user!
def create
#user = User.find(params[:user_id])
raise AccessDenied unless #user == current_user
#user.update(password: params[:password])
respond_with(#user)
end
end
This is a very simplified example - but the point is if you strip off all the junk from the controller related to forms / flashes and redirects there is not that much you are really going to re-use.
If your front-end app is a "classical" client/server Rails app then you can use a regular cookie based auth (Devise) and let it share the database with the API app. Token based auth does not work well with classical client/server apps due to its stateless nature.
If the front end app is a SPA like Angular or Ember.js you might want to look into setting up your own OAuth provider with Doorkeeper instead.
Im working on my Rails Backend in Ruby and i want to post Data to this server. But if i do a Post-request with PAW i get redirected. Im a newbie to Http Requests. Could someone explain me the functionality and how to use http post requests?
i want to post information on my server's datanase (sqlite3).
Here's a screenshot which should explain everything:
how does this work? please explain :)
thanks.
greetings John
and here's the code:
OwnersController:
#app/controllers/owners_controller.rb
class OwnersController < SessionsController
respond_to :html
before_action :owner_find, only: [:show, :edit, :update, :destroy]
def index
#owners = Owner.all
end
def show
end
def update
#owner = Owner.find(params[:id])
if #owner.update(owner_params)
redirect_to #owner
else
render 'edit'
end
end
def new
#owner = Owner.new
end
def destroy
#owner.destroy
redirect_to owners_path
end
def edit
end
def create
#owner = Owner.new owner_params
if #owner.save!
flash[:notice] = 'You signed up successfully'
flash[:color]= 'valid'
redirect_to owners_path
else
flash[:notice] = 'Form is invalid'
flash[:color]= 'invalid'
render 'new'
end
end
private
def owner_find
#owner = Owner.find(params[:id])
end
def owner_params
params.require(:owner).permit(:name, :password, :password_confirmation, :token)
end
end
SessionController:
class SessionsController < ApplicationController
before_filter :authenticate_user, :except => [:login, :login_attempt]
def login
#goes to Login Form
end
def logout
session[:owner_id] = nil
redirect_to :action => 'login'
end
def login_attempt
authorized_user = Owner.authenticate_by_name(params[:login_name],params[:login_password])
if authorized_user
session[:owner_id] = authorized_user.id
flash[:notice] = "Wow Welcome again, you logged in as #{authorized_user.name}"
redirect_to welcome_index_path
else
flash[:notice] = 'Invalid Username or Password'
flash[:color]= 'invalid'
render 'login'
end
end
end
Console Logs:
from web-request (http://192.168.2.144:3000/owners?name=hans&password=hans321&password_confirmation=hans321)
Started GET "/owners?name=hans&password=[FILTERED]&password_confirmation=[FILTERED]" for 192.168.2.144 at 2015-10-01 12:12:18 +0200
Cannot render console from 192.168.2.144! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by OwnersController#index as HTML
Parameters: {"name"=>"hans", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
Owner Load (0.1ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" = ? LIMIT 1 [["id", 2]]
Owner Load (0.1ms) SELECT "owners".* FROM "owners"
Rendered owners/index.html.erb within layouts/application (1.8ms)
Completed 200 OK in 60ms (Views: 58.9ms | ActiveRecord: 0.2ms)
It's telling 200 ok but nothing happens in the DB.
from Paw-Request (so i can use post. btw. how do i use post in browser request?
Started POST
"/owners?name=hans&password=[FILTERED]&password_confirmation=[FILTERED]"
for 192.168.2.144 at 2015-10-01 12:12:45 +0200 Cannot render console
from 192.168.2.144! Allowed networks: 127.0.0.1, ::1,
127.0.0.0/127.255.255.255 Processing by OwnersController#create as HTML Parameters: {"name"=>"hans", "password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"} Can't verify CSRF token
authenticity Redirected to http://192.168.2.144:3000/ Filter chain
halted as :authenticate_user rendered or redirected Completed 302
Found in 1ms (ActiveRecord: 0.0ms)
It seems that the CRSF authentication failed..
Edit:
at first:
to Rich Peck! This helped me so much. Thank you!! I really appreciate your effort.
Im near to the solution.. My problem is: i cant put the correct params in the url. The token-auth is disabled for testing. so it wont matter.
the params should be like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"q9JvFhoSUgfydFTvh18JHbIIdKNDjnOS9m/trVBu9EHPP04xGsO69zPh1BFZBI1Ev1YcnOTiPmaAiPWOSkm5Xg==", "owner"=>{"name"=>"Hubert", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create Owner"}
and not as in my request:
Parameters: {"name"=>"Hubert", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "owner"=>{}}
HTTP Status Codes
Firstly, a 30x response means "Resource Moved".
301 responses are used by many SEO people to denote permanent relocation of resources. 302 not so common, but still means a similar thing.
Every time you send & receive HTTP requests, you're going to receive a status code. The typical is the 200 response -- status success!
What you're seeing is the redirect_to command in action -
if #owner.save!
flash[:notice] = ...
redirect_to owners_path
I've never used PAW before, but I assume it's just giving you the pure response of the server, which would in this case be a 30x "Resource Moved" code.
I would expect a typical browser request to load the redirected route and display its yield on the screen.
Server
As a way to test this, you should attempt the same transaction in your browser:
lvh.me:3000/orders
(lvh.me is a domain routed to your own localhost which helps with subdomains in Rails)
This will give you the ability to test and see what happens with the responses. You *should * find that your data has been saved to the database (albeit SQLite3 in your case).
Syntax
Finally, you need to ensure you're using the correct syntax in your code.
Specifically:
#app/controllers/owners_controller.rb
class OwnersController < ApplicationController
...
def create
#owner = Owner.new owner_params
end
private
def owner_params
params.require(:owner).permit(:name, :password, :password_confirmation)
end
end
You'll also want to look at bcrypt-ruby for protecting your passwords.
Testing
I tend to just test my Rails apps with standard browser functionality.
This means you can run the Rails Server ($ rails s in your console), which you'll then be able to then access through your browser.
You're trying to use this PAW thing, which is okay, but doesn't give you much flexibility in regard to the user-interactivity of the app (for example, submitting real forms etc)...
In your case, I'd do the following:
#app/views/orders/new.html.erb
<%= form_for #order do |f| %>
<%= f.text_field :name %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= f.submit %>
<% end %>
You'd then access lvh.me:3000/orders/new and submit the form. This will show you how it responds!
HTTP
Okay here's the deal with HTTP requests...
Whenever you send a piece of transactional data to your web application, you do it through an HTTP request. HTTP requests are just a way to send data through the "Internet".
With Rails based apps, this means that every time you "do" something in the app, you're really sending an HTTP request to your web server. Rails interprets this request and sends a response. This response is what your question is about.
You're asking about receiving 302 responses - this is the web server's way of saying you've been redirected. It's pretty basic stuff to be honest; your browser handles most of it.
A great tutorial can be found here:
Alright then your error is as follows:
Can't verify CSRF token authenticity
I can elaborate more on this later, but for now, you might want to look up this solution: WARNING: Can't verify CSRF token authenticity in case of API development